小言_互联网的博客

Linux 多线程(线程控制(创建/终止/等待/分离))

458人阅读  评论(0)

目录

POSIX线程库

创建线程

线程ID及进程地址空间布局

线程终止

线程等待

线程分离


线程概念/特点/优缺点/与进程比较 写在另一篇博客

戳链接( ̄︶ ̄)↗https://blog.csdn.net/qq_41071068/article/details/104571277

POSIX线程库

POSIX线程(POSIX Threads, 常缩写为Pthreads)是POSIX线程标准, 定义了创建和操纵线程的一套API(应用程序编程接口).

由于Linux内核不区分线程和进程, 所以操作系统并没有向用户提供创建线程的接口, 为此大佬们封装了一套线程控制接口

  • POSIX线程库中绝大多数函数的名字都是以“pthread_”打头的
  • 要使用这些函数库,要加头文<pthread.h>
  • 在最后编译链接时, 要添加 -pthread或-lpthread选项, 用来链接POSIX线程库(注 : gcc4.5.2往后就已经没有-lpthread的介绍了, 所以往后选用-pthread)

创建线程

与创建进程的函数fork()类似, 其实fork()和pthread_create()内部都调用了clone(). 

原型 : int pthread_create(pthread_t* tid, const pthread_attr_t* attr, void* (*start_routine)(void* arg), void* arg) ;

功能 : 创建一个新线程

参数 :  tid : 输出型参数, 获取线程id(这个线程id下面细说)
           attr : 设置线程的属性,传入NULL表示使用默认属性
           start_routine : 是一个函数指针, 传入线程入口函数 , 其中arg是线程入口函数的形参( 创建一个线程就是为了运行传                                      入的函数指针所指向的函数, 函数运行完毕, 则线程退出)
           arg : 是传入的线程入口函数的参数

返回值 : 返回值为0, 表示创建成功, 返回值 > 0, 则表示失败, 返回的是errno

来看个例子 .


  
  1. #include<iostream>
  2. #include<cstdio>
  3. #include<cstring>
  4. #include<unistd.h>
  5. #include<pthread.h>
  6. void* thread_start(void* arg){
  7. while( 1){
  8. printf( "主线程向我传递了一个参数:%s\n", ( char*)arg);
  9. sleep( 1);
  10. }
  11. return NULL;
  12. }
  13. int main(){
  14. pthread_t tid;
  15. char buf[] = "哈哈哈哈";
  16. int ret = pthread_create(&tid, NULL, thread_start, ( void*)buf);
  17. if(ret){
  18. fprintf( stderr, "pthread_create: %s\n", strerror(ret)); //strerror函数
  19. //perror("pthread_create:");//如果置全局的errno, 则可以直接用perror,
  20. //但pthread_create并不会改变全局的errno, 而是将errno返回
  21. return -1;
  22. }
  23. while( 1){
  24. printf( "我是主线程\n");
  25. sleep( 1);
  26. }
  27. return 0;
  28. }


线程ID及进程地址空间布局

线程id和进程id

在没有学习线程之前, 我们将PCB中的pid作为一个进程的id来标识一个进程, 但当引入线程的概念后, 就不能再这样理解. 当

一个进程有多个线程时, 每个线程在内核中都有一个PCB, 每个PCB都有唯一的pid, 那么此时的进程id又是什么呢, 其实这

时候我们所说的进程id是就是PCB中tgid (thread group id), 同组线程PCB中的tgid都是相同的, 对应的就是我们能看见的进程

id, 而线程id就是其各自PCB唯一的pid(之前我们认为的进程id变为了现在的线程id), 就拿上面的图为例.

再来看pthread_create() 函数中所获取的线程id

  • pthread_ create函数会产生一个线程id, 存放在第一个参数指向的地址中. 这里的线程id和上面所说的线程id不是一回事.
     
  • 上面所说的线程id属于内核调度的范畴, 因为线程是轻量级进程, 是操作系统调度的最小单位, 所以需要一个唯一标识 .
     
  • 而这里pthread_create()函数第一个参数返回的值是指向一个虚拟内存单元的指针, 也就是说这个内存单元的首地址即为新创建线程的线程id
     
  • 那么这个线程id(指针)指向的内存单元到底是什么呢?由于线程库函数并不是内核提供的, 所以线程库函数无法直接对内核中
    线程的PCB操作, 所以在创建线程时, 在虚拟内存空间上也分配了一块空间来存储线程描述信息, 线程库函数通过在找到线程描述信息进而访问在内核中的线程的PCB, 现在就比较清楚了, 这里的线程id纯粹就是为了线程库函数要完成对线程的控制而存在的. 当脱离线程库函数后就失去意义. 
  •  

  • (mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一 一对映关系)
     
  • 线程库中其他函数的后续操作, 就是根据该线程ID来操作线程的.
     
  • 这样pthread_t 的类型也就知道了, pthread_t是一个指针类型.

pthread_ self函数,可以获得线程自身的ID

原型 : pthread_t pthread_self( )

功能 : 获取线程自身id

返回值 : 线程自身id


  
  1. #include<iostream>
  2. #include<cstdio>
  3. #include<cstring>
  4. #include<unistd.h>
  5. #include<pthread.h>
  6. void* thread_start(void* arg){
  7. sleep( 1);
  8. printf( "ID:%p,主线程向我传递了一个参数:%s\n", pthread_self(), ( char*)arg);
  9. return NULL;
  10. }
  11. int main(){
  12. pthread_t tid;
  13. char buf[] = "哈哈哈哈";
  14. int ret = pthread_create(&tid, NULL, thread_start, ( void*)buf);
  15. if(ret){
  16. fprintf( stderr, "pthread_create: %s\n", strerror(ret)); //strerror函数
  17. //perror("pthread_create:");//如果置全局的errno, 则可以直接用perror,
  18. //但pthread_create并不会改变全局的errno, 而是将errno返回
  19. return -1;
  20. }
  21. printf( "我是主线程, ID:%p, 创建的线程ID:%p\n", pthread_self(), tid);
  22. sleep( 1);
  23. return 0;
  24. }


 线程终止

如果需要只终止某个线程而不终止整个进程,可以有三种方法:

  • 线程函数中 return. 这种方法对主线程不适用, 从main函数中return相当于调用exit(), 是退出进程
  • 线程调用库函数pthread_ exit()终止自己.
  • 一个线程可以调用pthread_ cancel()可以终止同一进程中(线程组中)的另一个线程.

具体来看 

原型 : void pthread_exit(void* retval)

功能 : 谁调用, 谁退出

参数 : retval : 输出型参数, 返回线程的退出状态, 不关心可以置NULL. 

返回值 :

注意 : 不能将retval指向线程函数内的局部变量(临时变量), 一般是指向全局变量或是堆区的变量, return也是同样的道理.

原型 : int pthread_cancel(pthread_t tid)

功能 : 退出同组线程中, 线程id是tid的线程

参数 : tid : 线程id

返回值 : 为0, 则成功, 返回值>0, 返回的是错误码errno

注意 : 在线程入口函数中可以使用exit() 或 _exit()吗 ? 在线程中使用它们两都说使整个进程退出(线程组都退出). 所以一般不用.


线程等待

不难发现, 当return 或者pthread_exie()时, 返回的结果去哪儿了 ? 其实, 当线程退出后, 其空间并没有释放, 任然在进程的地

址空间内, 创建新的线程不会再复用刚才退出线程的地址空间(因为没有释放啊), 所以为了获取线程返回状态, 我们需要线程

等待.

原型 : int pthread_join(pthread_t tid, void** retval) 

功能 : 等待指定线程退出, 获取其退出状态

参数 : tid : 需要等待退出的线程id
          retval : 输出型参数, 二级指针, 指向线程退出时返回的指针, 线程退出时返回的指针指向线程退出状态(返回值).

返回值 : 成功返回0, 失败返回值>0, 返回的是错误码errno

注意 : 调用这个函数的线程将挂起等待(谁调用的谁挂起等待), 直到id为tid的线程终止. 线程id是tid的线程以不同的方法终止,

通过pthread_join得到的终止状态是不同的,分别如下:

  • return 退出, retval指向的是 return 返回的值
  • pthread_exit() 退出时, retval指向的是pthread_exit()的参数
  • 被pthread_cancel异常终止, retval的值是宏 PTHREAD_ CANCELED
  • 当不关心线程的推出状态时, retval的可以置NULL

举个栗子 :


  
  1. #include<iostream>
  2. #include<cstdio>
  3. #include<cstring>
  4. #include<unistd.h>
  5. #include<pthread.h>
  6. using namespace std;
  7. int n = 2;
  8. void* thread_start1(void* arg){
  9. int* p = new int;
  10. *p = 1;
  11. return ( void*)p;
  12. }
  13. void* thread_start2(void* arg){
  14. int *p = &n;
  15. pthread_exit(( void*)p);
  16. }
  17. void* thread_start3(void* arg){
  18. while( 1){
  19. sleep( 1);
  20. printf( "thread3 is runing ...\n");
  21. }
  22. return NULL;
  23. }
  24. int main(){
  25. pthread_t tid[ 3];
  26. void* (*thread_start[ 3])( void*) = {thread_start1, thread_start2, thread_start3};
  27. int ret;
  28. for( int i = 0; i < 3; ++i){
  29. ret = pthread_create(&tid[i], NULL, thread_start[i], NULL);
  30. if(ret){
  31. fprintf( stderr, "pthread_create%d: %s\n", i + 1, strerror(ret));
  32. return -1;
  33. }
  34. }
  35. int* retval;
  36. pthread_join(tid[ 0], ( void**)&retval);
  37. printf( "thread%d返回值为%d\n", 1, *retval);
  38. delete retval;
  39. pthread_join(tid[ 1], ( void**)&retval);
  40. printf( "thread%d返回值为%d\n", 2, *retval);
  41. sleep( 3);
  42. pthread_cancel(tid[ 2]);
  43. pthread_join(tid[ 2], ( void**)&retval);
  44. if(retval == PTHREAD_CANCELED){
  45. printf( "thread3 return code:PTHREAD_CANCELED\n");
  46. }
  47. else{
  48. printf( "thread%d返回值为%d\n", 2, *retval);
  49. }
  50. return 0;
  51. }

                


 线程分离

默认情况下,pthread_create() 新创建的线程是joinable的, 线程退出后, 需要对其进行pthread_join操作, 否则无法释放资源, 会造成内存泄漏 .

但当我们不关心线程的返回值, joinable属性就成为了一种负担, 这个时候, 我们可以告诉系统, 当线程退出时, 自动释放线程资源. 我们就不需要再管了.

原型 : int pthread_detach(pthread_t tid)

功能 : 分离指定的线程(将指定的线程属性设置为detach)

参数 : tid : 需要分离的线程id

返回值 : 成功返回0, 失败返回值>0, 返回的是错误码errno

注意 :

  • pthread_detach() 可以是线程组内其他线程对目标线程进行分离 : pthread_detach(tid) //tid为要分离的同组线程id
  • 也可以是线程自己分离 : pthread_detach(pthread_self()) 
  • 线程分离只是给线程更改一下属性, 所以可以在线程入口函数中让线程自己分离自己,  也可以在主线程中创建线程之后直接分离
  • 一个线程的属性不能使即joinable又detach. 当线程被分离后就不能再pthread_join()了. 等待一个已经被分离的线程, pyhread_join会返回错误

举个栗子 :


  
  1. #include<iostream>
  2. #include <cstdio>
  3. #include <cstring>
  4. #include <unistd.h>
  5. #include <pthread.h>
  6. using namespace std;
  7. void* thread_start1(void* arg) {
  8. pthread_t tid = pthread_self();
  9. int ret = pthread_detach(tid);
  10. if(ret){
  11. fprintf( stderr, "pthread_detach thread1:%s\n", strerror(ret));
  12. }
  13. else{
  14. printf( "thread1, id:%p已分离\n", tid);
  15. }
  16. usleep( 1000);
  17. return NULL;
  18. }
  19. void* thread_start2(void* arg){
  20. usleep( 1000);
  21. return NULL;
  22. }
  23. void* thread_start3(void* arg){
  24. pthread_t tid = pthread_self();
  25. int ret = pthread_detach(tid);
  26. if(ret){
  27. fprintf( stderr, "pthread_detach thread3:%s\n", strerror(ret));
  28. }
  29. else{
  30. printf( "thread3, id:%p已分离\n", tid);
  31. }
  32. usleep( 1000);
  33. return NULL;
  34. }
  35. int main() {
  36. pthread_t tid;
  37. //thread1
  38. int ret = pthread_create(&tid, NULL, thread_start1, NULL);
  39. if (ret){
  40. fprintf( stderr, "pthread_create thread1:%s\n", strerror(ret));
  41. return -1;
  42. }
  43. //thread2
  44. usleep( 100);
  45. ret = pthread_create(&tid, NULL, thread_start2, NULL);
  46. if (ret){
  47. fprintf( stderr, "pthread_create thread2:%s\n", strerror(ret));
  48. return -1;
  49. }
  50. ret = pthread_detach(tid);
  51. if(ret){
  52. fprintf( stderr, "pthread_detach thread2:%s\n", strerror(ret));
  53. }
  54. else{
  55. printf( "thread2, id:%p已分离\n", tid);
  56. }
  57. //thread3
  58. ret = pthread_create(&tid, NULL, thread_start3, nullptr);
  59. if (ret){
  60. fprintf( stderr, "pthread_create thread3:%s\n", strerror(ret));
  61. return -1;
  62. }
  63. usleep( 1000); //必须要睡一下,要让线程先分离,再等待
  64. //线程被创建, 但创建的线程中的分离语句和下面的join语句
  65. //哪个先执行还真不一定, 所以说要先睡一下
  66. ret = pthread_join(tid, NULL);
  67. if (ret == 0) {
  68. printf( "pthread3 wait success\n");
  69. } else {
  70. fprintf( stderr, "pthread3 wait failed:%s\n", strerror(ret));
  71. }
  72. return 0;
  73. }

     

 

 


转载:https://blog.csdn.net/qq_41071068/article/details/104613224
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场