Linux下的多线程编程一(系统编程)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/snow_5288/article/details/73028514

一,线程的基本概念
1,线程的定义:
线程也被称为轻量进程(LWP)计算机科学术语,指运行中的程序的调度单位。

线程是进程中的实体,一个进程可以拥有多个线程,一个线程必须有一个父进程。线程不拥有系统资源,只有运行必须的一些数据结构;它与父进程的其它线程共享该进程所拥有的全部资源。线程可以创建和撤消线程,从而实现程序的并发执行。一般,线程具有就绪、阻塞和运行三种基本状态。

在多中央处理器的系统里,不同线程可以同时在不同的中央处理器上运行,甚至当它们属于同一个进程时也是如此。大多数支持多处理器的操作系统都提供编程接口来让进程可以控制自己的线程与各处理器之间的关联度。

2,线程与进程的对比
1>定义
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。

线程比进程的粒度更细,线程是进程执行流中的分支,拥有进程一部分资源。

2>关系
一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行。

相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。

3>区别:
a.地址空间和其它资源:进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
b.通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
c.调度和切换:线程上下文切换比进程上下文切换要快得多。
d.在多线程OS中,进程不是一个可执行的实体。

4>优缺点
线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而进程正相反。同时,线程适合于在SMP机器上运行,而进程则可以跨机器迁移。

3,多线程
这里写图片描述

main函数和信号处理函数是同一个进程地址空间中的多个控制流程,多线程也是如此,但是比信号处理函数更加灵活,信号处理函数的控制流程只是在信号递达时产生,在处理完信号之后就结束,而多线程的控制流程可以长期并存,操作系统会在各线程之间调度和切换,就像在 多个进程之间调度和切换一样。

同一进程的多个线程之间共享以下进程资源和环境:
1. 文件描述符表
2. 每种信号的处理方式(SIG_IGN、SIG_DFL或者自定义的信号处理函数)
3. 当前工作目录
4. 用户id和组id

同一进程的多个线程之间的私有资源:
1. 线程id(是一个正整数,仅在当前进程内有效,用来标识线程)
2. 上下文,包括各种寄存器的值、程序计数器和栈指针
3. 栈空间
4. errno变量
5. 信号屏蔽字
6. 调度优先级

我将要介绍的线程库函数是由POSIX标准定义的,称为POSIX thread或者pthread。在Linux上线程函数位于libpthread共享库中,因此在编译时要加上-lpthread选项

二,线程控制

1,线程创建

#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);

返回值:成功返回0,错误返回错误号
第一个参数thread:线程id(输出型参数)
第二个参数attr:线程属性,一般设置为NULL(表示线程属性取缺省值)
第三个参数start_routine:函数指针,指向新线程即将执行的代码
第四个参数arg:这个指针按什么类型解释由调用者自己定义—->NULL

在一个线程中调用pthread_create()创建新的线程后,当前线程从pthread_create()返回继续往下执行,而新的线程所执行的代码由我们传给pthread_create的函数指针start_routine决定。

代码举例:
这里写图片描述

由上例可知在Linux上,pthread_t类型是一个地址值,属于同一进程的多个线程调用getpid()可以得到相同的进程号,而调用pthread_self()得到的线程号各不相同。
如果任意一个线程调用了exit或_exit,则整个进程的所有线程都终止,由于从main函数return也相当于调用exit(下面解释)。

运行结果:
这里写图片描述

2,终止线程
1>如果需要只终止某个线程而不终止整个进程,可以有三种方法:
1. 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。
2. 一个线程可以调用pthread_cancel终止同一进程中的另一个线程。
3. 线程可以调用pthread_exit终止自己。

2>终止线程或执行流:

#include <pthread.h>
void pthread_exit(void *retval);

retval是void *类型,和线程函数返回值的用法一样,其它线程可以调用pthread_join(稍后介绍)获得这个指针。

需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。

3>取消线程

#include <pthread.h>
int pthread_cancel(pthread_t thread);

线程是允许被取消的,退出结果为-1,线程自己取消自己(不推荐)退出结果为0。

3,线程等待
1>为什么要等待线程?
main函数执行的线程称为主线程,多线程的执行顺序由线程调度决定
主线程必须回收其他线程,否则就会产生类似僵尸进程的状况(内存泄漏)。
结论:线程必须被等待

2>获取当前线程的线程tid:pthread_self

 #include <pthread.h>
 pthread_t pthread_self(void);

(仅仅在当前进程内部有效,作为线程的唯一标识符)

3>线程等待函数:

#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);

返回值:成功返回0,失败返回错误码。
参数thread:线程号,即要等待线程的tid
参数retval:要等待线程的退出码(输出型参数)

调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:
1. 如果thread线程通过return返回,value_ptr所指向的单元里存放的是thread线程函数的返回值。
2. 如果thread线程被别的线程调用pthread_cancel异常掉,value_ptr所指向的单元里存放的是常数PTHREAD_CANCELED。
3. 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ptr参数。

4>多线程的进行执行时,只要有一个线程出错,整个进程就会挂掉(操作系统会向其发信号回收资源,其他线程都跟着退出)。线程运行时,线程只能正常的运行完,退出码表明了其运行状态
线程等待只有一种方式:阻塞式等待

4,综合举例

1 #include<stdio.h>
  2 #include<pthread.h>
  3 #include<unistd.h>
  4 
  5 void* thread1(void* val1)
  6 {
  7     printf("thread1 is returning\n");
  8     printf("%s:pid is %d,tid is %u\n",(char*)val1,getpid(),pthread_self());
  9     return (void*)0;//线程终止方式1,用return返回
 10 }
 11 void* thread2(void* val2)
 12 {
 13     printf("thread2 exiting\n");
 14     printf("%s:pid is %d,tid is %u\n",(char*)val2,getpid(),pthread_self());
 15     pthread_exit((void*)2);//线程终止方式2,用pthread_exit退出
 16 }
 17 void* thread3(void* val3)
 18 {
 19     printf("%s:pid is %d,tid is %u\n",(char*)val3,getpid(),pthread_self());
 20     while(1)
 21     {    22         printf("thread3 is running,waiting for be canceled\n");//线程终止方式3,被其他线程c    ancel   
 23         sleep(1);
 24     }
 25 }
 26 int main()
 27 {
 28      pthread_t tid1;
 29      pthread_t tid2;
 30      pthread_t tid3;
 31      void* ret;
 32 
 33      //thread1 return
 34      pthread_create(&tid1,NULL,thread1,"thread1");//线程1创建
 35      pthread_join(tid1,&ret);//wait thread1
 36      printf("thread1 return,return code is %d\n",(int)ret);
 37 
 38      //thread2 exit
 39      pthread_create(&tid2,NULL,thread2,"thread2");//线程2创建
 40      pthread_join(tid2,&ret);//wait thread2
 41      printf("thread2 exit,exit code is %d\n",(int)ret);
 42 
 43      //thread3 cancel
 44      pthread_create(&tid3,NULL,thread3,"thread3");//线程3创建
 45      sleep(3);
 46      pthread_cancel(tid3);//线程终止方式3,被其他线程用thread_cancel取消
 47      pthread_join(tid3,&ret);//wait thread3
 48      printf("thread3 cancel,cancel code is %d\n",(int)ret);
 49 
 50      printf("main thread run:pid is %d,tid is %u\n",getpid(),pthread_self());
 51 
 52      return 0;
 53 }

运行结果:
这里写图片描述

细心一点的朋友们就会发现,为什么3个线程的pid和tid都是相同的呢?原因就是pid实际上是主线程的进程号,同为1514,那么既然是不同的线程,又因为线程号是确定一个线程的标识,为什么3个线程号仍然相同呢?
其实不然,回顾代码我们是验证3种线程终止的方式,而且我们在每个线程终止后就对其进行了等待,调用过线程等待函数之后主线程就会回收其资源,其中当然也包括线程id,当线程1回收之后,我们紧接着创建了线程2,系统会优先选择刚刚回收的1号线程id来分配给线程2,当线程2终止等待之后,此线程号再次被回收利用,进而分配给线程3,这就是为什么我们看到不同的3个线程拥有同一个tid了。

如果你对上述问题还有质疑,请看下面的代码:

  1 #include<stdio.h>
  2 #include<pthread.h>
  3 #include<unistd.h>
  4 
  5 void* thread1(void* val1)
  6 {
  7     printf("thread1 is returning\n");
  8     printf("%s:pid is %d,tid is %u\n",(char*)val1,getpid(),pthread_self());
  9     return (void*)0;//线程终止方式1,用return返回
 10 }
 11 void* thread2(void* val2)
 12 {
 13     printf("thread2 exiting\n");
 14     printf("%s:pid is %d,tid is %u\n",(char*)val2,getpid(),pthread_self());
 15     pthread_exit((void*)2);//线程终止方式2,用pthread_exit退出
 16 }
 17 void* thread3(void* val3)
 18 {
 19     printf("%s:pid is %d,tid is %u\n",(char*)val3,getpid(),pthread_self());
 20     while(1)
 21     {
 22         printf("thread3 is running,waiting for be canceled\n");//线程终止方式3,被其他线程c    ancel
 23         sleep(1);
 24     }
 25 }
 26 int main()
 27 {
 28      pthread_t tid1;
 29      pthread_t tid2;
 30      pthread_t tid3;
 31      void* ret;
 32 
 33      //thread1 return
 34      pthread_create(&tid1,NULL,thread1,"thread1");//线程1创建
 35 //     pthread_join(tid1,&ret);//wait thread1
 36      printf("thread1 return,return code is %d\n",(int)ret);
 37 
 38      //thread2 exit
 39      pthread_create(&tid2,NULL,thread2,"thread2");//线程2创建
 40 //     pthread_join(tid2,&ret);//wait thread2
 41      printf("thread2 exit,exit code is %d\n",(int)ret);
 42 
 43      //thread3 cancel
 44      pthread_create(&tid3,NULL,thread3,"thread3");//线程3创建
 45      sleep(3);
 46      pthread_cancel(tid3);//线程终止方式3,被其他线程用thread_cancel取消
 47 //   pthread_join(tid3,&ret);//wait thread3
 48      printf("thread3 cancel,cancel code is %d\n",(int)ret);
 49 
 50      printf("main thread run:pid is %d,tid is %u\n",getpid(),pthread_self());
 51 
 52      pthread_join(tid1,NULL);
 53      pthread_join(tid2,NULL);
 54      pthread_join(tid3,NULL);
 55      return 0;
 56 }

调整代码:将程序中间对线程的等待注释掉,在程序运行即将结束时对3个线程进行等待,在此期间3个子线程处于僵死状态。
运行结果:
这里写图片描述

5,线程属性
线程的两种属性:可结合性和可分离性。
在任何一个时间点上,线程是可结合的(joinable)或者是分离的(detached)。一个可结合的线程能够被其他线程收回其资源和杀死。在被其他线程回收之前,它的存储器资源(例如栈)是不释放的。相反,一个分离的线程是不能被其他线程回收或杀死的,它的存
储器 资源在它终止时由系统自动释放。

默认情况下,线程被创建成可结合的。
为了避免存储器泄漏,每个可结合线程都应该要么被显示地回收,即调用pthread_join;要么通过调用pthread_detach函数被分离。

由于调用pthread_join后,如果该线程没有运行结束,调用者会被阻塞,在有些情况下我们并不希望如此。改善方法:
1>可以在子线程中加入代码:

pthread_detach(pthread_self())

2>父线程调用:

pthread_detach(thread_id)(非阻塞,可立即返回)

这将该子线程的状态设置为分离的(detached),detached),如一来,该线程运行结束后会自动释放所有资源。

线程可以主动分离,也可以被分离。
一个进程设置为分离线程,则主线程不需要对其等待,待其运行完系统会自动回收。

线程分离代码:

  1 /**************************************
  2 *文件说明:thread_detach.c
  3 *作者:段晓雪
  4 *创建时间:2017年05月22日 星期一 20时53分18秒
  5 *开发环境:Kali Linux/g++ v6.3.0
  6 ****************************************/
  7 #include<stdio.h>
  8 #include<unistd.h>
  9 #include<pthread.h>
 10 #include<string.h>
 11 
 12 void* thread4(void* val4)
 13 {
 14     printf("%s:thread4 child detach,thread4 tid is %d\n",(char*)val4,pthread_self());//线程
    4主动分离
 15     pthread_detach(pthread_self());
 16     return (void*)0;//线程终止
 17 }
 18 void* thread5(void* val5)
 19 {
 20     printf("%s:thread5 father detach,thread5 tid is %d\n",(char*)val5,pthread_self());//线>    程5被分离
 21     return (void*)0;//线程终止
 22 }
 23 
 24 int main()
 25 {
 26      pthread_t tid4;
 27      int ret = pthread_create(&tid4,NULL,thread4,"thread4 is running");//线程创建
 28      if(ret != 0)
 29          printf("creat thread4 error:errno is %d,error info is %s\n",ret,strerror(ret));
 30      //wait---线程等待
 31      sleep(1);
 32      int tmp = 0;
 33      tmp = pthread_join(tid4,NULL);
 34      if(0 == tmp)
 35          printf("thread4 wait success\n");
 36      else
 37          printf("thread4 wait failure\n");
 38 
 39      pthread_t tid5;
 40      ret = pthread_create(&tid5,NULL,thread5,"thread5 is running");//线程创建
 41          if(ret != 0)
 42                 printf("creat thread5 error:errno is %d,error info is %s\n",ret,strerror(re    t));
 43      sleep(1);
 44          tmp = pthread_detach(tid5);//线程分离
 45      if(0 == tmp)
 46      {
 47          printf("thread5 is detached success\n");
  48      }
 49      else 
 50      {   
 51          printf("thread5 is detached failure\n");
 52          tmp = pthread_join(tid5,NULL);//线程等待
 53          if(0 == tmp)
 54              printf("thread5 wait success\n");
 55          else
 56              printf("thread5 wait failure\n");
 57      }           58      return 0;
 59 } 

运行结果:
这里写图片描述

展开阅读全文

没有更多推荐了,返回首页