【Linux】初识线程

2019-07-14 11:48发布

一、什么是线程         1、LWP:light weight process  轻量级的进程,本质仍是进程(在 Linux 环境下)         2、进程:有独立地址空间,拥有 PCB          3、线程:也有 PCB,但没有独立的地址空间(共享)         4、区别:在于是否共享地址空间。例如:独居(进程),合租(线程)。         5、在 Linux  线程是是 CPU 调度和执行的最小单位,进程是系统分配资源和调度的最小单位,可以看成只有一个线程的进
二、Linux 内核线程实现原理          1、轻量级进程(light-weight process ),也有 PCB,创建进程使用的底层函数和进程一样,都是 clone。          2、从内核里看进程和线程是一样的,都有各自不同的 PCB ,但是 PCB 中指向内存资源的三级页表是相同的。          3、进程可以蜕变成线程。          4、线程可看做栈和寄存器的集合。          5、在 Linux 下,线程是最小的执行单位;进程是最小的资源分配单位。           查看 LWP 号: ps -Lf pid 查看指定线程的   lwp 号 三、线程共享资源        1、文件描述符。         2、每种信号的处理方式。         3、当前工作目录。         4、用户 ID 和组 ID。         5、内存地址空间(.text/.data/.bss/heap/共享库) 四、线程非共享资源         1、线程 id         2、处理器现场和栈指针(内核栈)         3、独立的用户空间(用户空间栈)         4、errno变量         5、信号屏蔽字         6、调度优先级 五、线程优、缺点        1、优点:                    (1)、提高程序并发性                              (2)、开销小                    (3)、数据通信、共享数据方便        2、缺点:                    (1)、库函数、不稳定                    (2)、调试、编写困难、gdb 调试不支持                    (3)、对信号支持不好 六、线程控制原语        1、pthread_self 函数              作用: 获取线程 ID ,其作用对应进程中的 getpid() 函数。              函数原型:                               #include pthread_t pthread_self(void);              返回值:                           成功,返回 0 ;失败:无返回值。              线程 ID: pthread_t 类型,本质:在 Linux下无符号整数 (%lu),其他系统中可能是结构体实现。              线程 ID 是进程内部,识别标志。(两个进程,允许线程 ID 相同)              注意:                      不应使用全局变量 pthread_t tid,在子线程中通过 pthread_create 传出参数来获取线程 ID,而应使用 pthread_self。
#include #include #include #include #include void *thd_func(void *arg) { printf("In thread id = %lu,pid = %u ",pthread_self(),getpid()); return NULL; } int main(void) { pthread_t tid; int ret; printf("In main 1: id = %lu,pid = %u ",pthread_self(),getpid()); ret = pthread_create(&tid,NULL,thd_func,NULL); if( ret !=0 ) { fprintf(stderr,"pthread_create error:%s ",strerror(ret)); exit(1); } sleep(1); printf("In main2: id = %lu,pid = %u ",pthread_self(),getpid()); return 0; }

       2、pthrea_create函数              作用:创建一个新的线程,其作用对应 进程中的 fork () 函数。              函数原型:                              #include int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
             返回值:                          成功:返回 0 ;失败:返回错误号             --------------------------- 在Linux 下所有和线程相关的控制原语失败均返回 错误号。              参数:                      pthread_t:当前Linux 中可以理解为: typedef unsigned long int pthread_t                      参数1:传出参数,保存系统为我们分配好的线程 ID。                      参数2:通常传 NULL,表示使用线程默认属性。若想使用具体属性也可以修改该参数。                      参数3:函数指针,指向主线程函数(线程体),该函数运行结束,则线程结束。                      参数4:线程主函数执行期间所使用的参数。                   在一线程中调用 pthread_create() 创建新的线程后,当线程从 pthread_create() 返回继续往下执行,而新的线程所执行的代码由我们所传给 pthread_create()的函数指针决定。pthread_create 创建成功返回后,新创建的线程 ID 被填写到 thread 参数所指向的内存单元。                     attr 参数表示线程属性。
           循环创建  N 个线程,示例代码:                     /* 循环创建 N 个子进程 */ #include #include #include #include #include void *thd_func(void *arg) { int i = (int)arg; sleep(i); printf("%dthd id = %lu,pid = %u ",i+1,pthread_self(),getpid()); return NULL; } int main(void) { pthread_t tid; int ret; int i; for( i=0;i<5;i++ ) { ret = pthread_create(&tid,NULL,thd_func,(void*)i); if( ret !=0 ) { fprintf(stderr,"pthread_create error:%s ",strerror(ret)); exit(1); } } sleep(i); return 0; }                         3、线程共享:                  线程间共享全局变量。                  注意:线程默认共享数据段、代码段等地址空间,常用的是全局变量。而进程不共享全局变量。只能借助 mmap            4、pthread_exit 函数                  作用:将单个线程退出                  函数原型:
#include void pthread_exit(void *retval);                   参数:                            retval 表示线程退出状态,通常传 NULL。                    注意:                                线程中禁止使用 exit 函数,会导致进程内的所有线程全部退出。在多线程环境中,应尽量少用或者不用 eixt 函数。取而代之,使用 pthread_exit 函数,将单个线程退出。任何线程里 exit 导致进程退出,其他线程未工作结束,主控线程退出时不能 return 或 exit。                                pthread_exit 或者 return  返回的指针所指向的内存单元必须是全局的或者是 malloc 分配的,不能再线程函数的栈上分配,因为当其他函数得到这个返回指针时线程函数已经退出了。             5、pthread_join 函数                   作用:阻塞等待线程退出,获取线程退出状态   ,其作用对应于进程中的 waitpid()函数。                   函数原型:                          #include int pthread_join(pthread_t thread, void **retval);                    返回值:                                 成功:返回 0 ; 失败:返回错误号                     参数:                                  thread      线程 ID(注意不是指针)                                  retval        存储线程结束状态。                      对比进程和线程记忆:                                  进程中: main 返回值、exit 参数 ------> int;   等待子进程结束   wait  函数参数 ------> int *                                  线程中: 线程主函数返回值、pthread_exit ---------->void *;等待子线程结束  pthread_join   函数参数 ---------->void **
#include #include #include #include #include typedef struct { char ch; int var; char str[64]; }exit_t; void *thd_func(void *arg) { exit_t *retval = (exit_t*)arg; retval->ch = 'm'; retval->var = 200; strcpy(retval->str,"my Thread "); pthread_exit((void*)retval); return NULL; } int main(void) { pthread_t tid; int ret; exit_t *retval = (exit_t*) malloc(sizeof(exit_t)); ret = pthread_create(&tid,NULL,thd_func,(void*)retval); if( ret !=0 ) { fprintf(stderr,"pthread_create error:%s ",strerror(ret)); exit(1); } pthread_join(tid,(void**)&retval); printf("ch = %c,var = %d,str = %s ",retval->ch,retval->var,retval->str); free(retval); return 0; }
           6、pthread_detach 函数                 作用:实现线程分离                 函数原型:                             #include int pthread_detach(pthread_t thread);
                  返回值:                                成功,返回0;失败:返回错误号                   注意:                            (1)、线程分离状态:指定该状态,线程主动与主控线程断开关系。线程结束后,其退出状态不由其他线程获取,而直接自己自动释放。网络、多线程服务中常用。                            (2)、进程若有该机制,将不会产生僵尸进程。僵尸进程的产生主要由于进程死后,大部分资源被释放,一点残留资源人存留在系统中,导致内核认为该进程仍存在。                            (3)、也可使用 pthread_create 函数参数 2 (线程属性)来设置线程分离。                    #include #include #include #include #include void *thd_func(void *arg) { int n = 3; while(n--) { printf("thread count = %d ",n); sleep(1); } // return (void*)1; pthread_exit((void*)1); // return 和 pthread_exit 的作用相同 } int main(void) { pthread_t tid; void *tret; int err; pthread_create(&tid,NULL,thd_func,NULL); pthread_detach(tid); // 让线程分离--------自动退出,无系统残留资源 while(1) { err = pthread_join(tid,&tret); printf("-------------- err = %d ",err); if( err != 0 ) fprintf(stderr,"thread_join error: %s ",strerror(err)); else fprintf(stderr,"thread exit code %d ",(int)tret); sleep(1); } return 0; }  
            7、pthread_cancel 函数                  作用 ;杀死(取消)线程,其作用对应于进程中的 kill() 函数。                  函数原型:                              #include int pthread_cancel(pthread_t thread);
                 返回值:                              成功:返回 0;失败:返回错误号                   注意:                            (1)、线程的取消不是实时的,而有一定的时延。需要等待线程达到某个取消点(检查点)。                            (2)、取消点:是线程检查是否被取消,并按请求进行动作的一个位置。通常是一些系统调用 create、open、pause、close、read、write......执行命令 man 7  threads  可查看具备这些取消点的系统调用列表。                            (3)、可以粗略的认为一个系统调用(进入内核)即为一个取消点。如线程中没有取消点,可以通过调用 pthread_testcancel 函数自行设置一个取消点。                            (4)、被取消的线程,退出定义在 Linux 的 pthread 库中,常数 PTHREAD_CANCEL 的值是 -1 ,可在头文件 pthread.h 中只找到它的定义 #define  PTHREAD_CANCEL ((void*)-1)。因此,当我们对一个已经被取消的线程使用 pthread_join 回收时,得到的返回值为 -1 。            七、线程使用注意事项h        1、主线程退出时其他线程不退出,主线程应该调用 pthread_exit        2、避免僵尸线程              pthread_join()              pthread_detach()              pthread_create() 指定分离属性              被 Join 线程肯能在 join 函数返回前就释放自己的所有内存资源,所以不应当返回被回收线程中的值。         3、malloc 和 mmap 申请的内存可以被其他线程释放         4、应避免在多线程模型中调用 fork 除非,马上 exec ,子进程中只有调用 fork 的线程存在,其他线程在子进程中均 pthread_exit         5、信号的复杂语义很难和多线程共存,应避免在多线程中引入信号机制。