线程概述
    与进程(process)类似,线程(thread)是允许应用程序并发执行多个任务的一种机制。
    一个进程可以包含多个线程。同一个程序中的所有线程均会独立执行相同的程序,且共享同一份全局内存区域,其中包括初始化数据段、未初始化数据段,以及堆内存段。
    (传统意义上的unix进程只是多线程的一个特例 该进程只包含一个线程)
    进程是cpu分配资源的最小单位,线程是OS调度执行的最小单位。
    线程是轻量级的进程LWP(light weight process)在linux环境下线程的本质仍是进程。
    查看指定进程的LWP号 ps -Lf 指定进程的pid (PID都一样 但每个线程都有自己的LWP号)

    为什么有了进程后还有有线程
    进程间的信息难以共享。对于父子进程来说除去只读代码区外,父子进程并未共享内存,因此必须采用一些进程间通信方式,在进程间进行信息交换。
    调用fork()来创建进程的代价相对较高,即使利用写时拷贝技术,仍需要复制诸如内存页表和文件描述符表之类的多种进程属性,这意味着fork()调用在时间上的开销依然不菲。
    线程之间能够方便、快速地共享信息。只需将数据复制到共享(全局或堆malloc)变量中即可。
    创建线程比创建进程通常要快10倍甚至更多。线程间是共享虚拟地址空间的,无需采用写时拷贝来复制内存也无需复制页表。

    所有线程共用同一块虚拟地址空间,但是会给不同线程划分栈空间和.text代码段的空间(将栈空间和.text分成小块分给这些线程),main线程(主线程)的栈、线程1的栈… main线程的代码,线程1的代码…,其他部分都是共享相同的。
    图片1.png
    线程间共享的资源
    进程PID 父进程ID PPID
    进程组ID 会话ID
    用户ID 用户组ID
    文件描述符表
    信号处置 (信号处理)
    文件系统相关信息:文件权限掩码(mask)、当前工作目录
    虚拟地址空间(除栈和.text段)

    非共享资源
    线程ID
    信号掩码 (阻塞信号集)
    线程特有数据
    error变量
    实时调度策略和优先级
    栈、本地变量和函数的调用链接信息

    查看当前pthread库(NPTL)版本 getconf GNU_LIBPTHREAD_VERSION

    创建线程
    man pthread_加tab可以查看基本上所有线程相关的函数
    #include
    int pthread_create(pthread_t thread, const pthread_attr_t attr,
    void (start_routine) (void ), void arg);
    pthread_t pthread_self(void);
    int pthread_equal(pthread_t t1, pthread_t t2);
    void pthread_exit(void retval);
    int pthread_join(pthread_t thread, void *
    retval);
    int pthread_detach(pthread_t thread);
    int pthread_cancel(pthread_t thread);

    1. /*
    2. #include <pthread.h>
    3. int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
    4. void *(*start_routine) (void *), void *arg);
    5. 创建一个子线程(main为主线程)
    6. thread 传出参数,当子线程创建成功后线程id会由这个指针传出
    7. attr 设置的线程属性 使用默认值 传入null
    8. start_routine 子线程所要处理的代码的函数的指针
    9. arg 为start_routine传参
    10. 返回值 成功返回0 失败返回错误号(man3中查看) 这个错误号和之前errno不太一样 不能通过perror输出
    11. 获取出错误号的信息 在string.h中char* strerror(int errnum)来获得这个错误号对应的错误信息
    12. 注意线程pthread库并非linux gcc自带库 libpthread.so 在编译时需要指定-pthread
    13. gcc learn_pthread_create.c -0 create -pthread
    14. gcc learn_pthread_create.c -0 create -l pthread
    15. 都对
    16. */
    17. #include <stdio.h>
    18. #include <pthread.h>
    19. #include <string.h>
    20. #include <unistd.h>
    21. void *callback(void *arg);
    22. int main()
    23. {
    24. // 创建一个子线程
    25. pthread_t tid;
    26. int num = 10; //可以搞个结构体将函数的参数全部放在那个结构体里 再往回调函数内传
    27. int ret = pthread_create(&tid, NULL, callback, (void *)&num);
    28. if (ret != 0)
    29. {
    30. printf("%s\n", strerror(ret));
    31. }
    32. for (int i = 0; i < 5; i++)
    33. {
    34. printf("parent thread\n");
    35. }
    36. sleep(1); //防止主线程结束了 子线程的callback还没执行
    37. //当主线程return 相当于进程exit所有子线程全部结束!!!
    38. return 0;
    39. }
    40. void *callback(void *arg)
    41. {
    42. printf("child thread\n");
    43. printf("arg value %d\n", *(int *)arg);
    44. return NULL;
    45. }

    终止线程

    1. /*
    2. #include <pthread.h>
    3. void pthread_exit(void *retval);
    4. 终止一个线程 在哪个线程中调用 就是终止哪个线程
    5. retval 传出参数 可被pthread_join函数获取该线程的返回值
    6. pthread_t pthread_self(void)
    7. 获取当前线程的线程ID
    8. int pthread_equal(pthread_t t1, pthread_t t2);
    9. 判断t1与t2的线程号是否相等 为什么不用==判断呢
    10. 因为不同OS pthread_t类型的实现不一样 有的是无符号长整型(当前Linux系统中是这种类型) 有的是结构体实现的 结构体就不能==了
    11. 注意线程pthread库并非linux gcc自带库 libpthread.so 在编译时需要指定-pthread
    12. gcc learn_pthread_create.c -0 create -pthread
    13. gcc learn_pthread_create.c -0 create -l pthread
    14. 都对
    15. */
    16. #include <stdio.h>
    17. #include <pthread.h>
    18. #include <string.h>
    19. #include <unistd.h>
    20. void *callback(void *arg);
    21. int main()
    22. {
    23. // 创建一个子线程
    24. pthread_t tid;
    25. int num = 10; //可以搞个结构体将函数的参数全部放在那个结构体里 再往回调函数内传
    26. int ret = pthread_create(&tid, NULL, callback, (void *)&num);
    27. if (ret != 0)
    28. {
    29. printf("%s\n", strerror(ret));
    30. }
    31. for (int i = 0; i < 5; i++)
    32. {
    33. printf("parent thread\n");
    34. }
    35. pthread_exit(NULL); //让子线程退出 当主线程退出时 不会影响其他正常运行的线程
    36. //主线程结束 pthread_exit下面的代码是不会执行的 即不会打印main thread exsit
    37. printf("main thread exsit\n"); //不会执行
    38. //当主线程return 相当于进程exit所有子线程全部结束!!!
    39. return 0; //不会执行
    40. // 主线程、子线程调用exit, pthread_exit,互相产生的影响。
    41. // 1、在主线程中,在main函数中return了或是调用了exit函数,则主线程退出,且整个进程也会终止,
    42. // 此时进程中的所有线程也将终止。因此要避免main函数过早结束。
    43. // 2、在主线程中调用pthread_exit, 则仅仅是主线程结束,进程不会结束,进程内的其他线程也不会结束,
    44. // 直到所有线程结束,进程才会终止。
    45. // 3、在任何一个线程中调用exit函数都会导致进程结束。进程一旦结束,那么进程中的所有线程都将结束。
    46. /**
    47. * 1: 线程使用return (这种方法对线程还适用,从main函数return 相当于调用exit
    48. 但是在子线程中return 相当于调用pthread_exit 除了最后一个子线程return 那也相当于exit)
    49. * 2: 调用pthread_cancel (一个线程可以调用pthread_cancel终止同一进程中的另一个线程)
    50. * 3: 调用pthread_exit(线程可以调用pthread_exit终止自己,有两种情况需要注意:
    51. * 一种情况是,在主线程中,如果从main函数返回或是调用了exit函数退出主线程,
    52. * 则整个进程将终止,此时进程中有线程也将终止,因此在主线程中不能过早地从main
    53. * 函数返回;
    54. * 另外一种情况:如果主线程调用pthread_exit函数,则仅仅是主线程消亡,
    55. * 进程不会结束,进程内的其他子线程也不会终止,直到所有线程结束,进程才会结束;
    56. * 线程终止最重要的问题是资源释放问题,特别是一些临界资源在一段时间内只能被
    57. * 一个线程所持有,当线程要使用临界资源需提出请求,如果该资源未被使用则申请
    58. * 成功,否则等待。临界资源使用完毕后要释放以便其它线程可以使用。
    59. **/
    60. }
    61. void *callback(void *arg)
    62. {
    63. printf("child thread\n");
    64. printf("arg value %d\n", *(int *)arg);
    65. pthread_t id = pthread_self();
    66. printf("id: %ld\n", id);
    67. return NULL; //相当于pthread_exit(NULL)
    68. }

    连接已终止的线程

    1. /*
    2. #include <pthread.h>
    3. int pthread_join(pthread_t thread, void **retval);
    4. 连接已经终止的线程 回收子线程的资源 此函数阻塞
    5. 没有线程结束这个函数一直阻塞 直到有线程结束此函数回收后才能运行下面的语句
    6. 调用一次只能回收一个线程 一般在主线程中使用
    7. 参数 thread需要回收的线程ID retval 接收子线程退出时的返回值 二级指针
    8. 线程结束时(通过pthread_exit退出传入返回值)这个返回值可被retval得到
    9. 返回值 成功返回0 失败返回错误号(man3中查看) 这个错误号和之前errno不太一样 不能通过perror输出
    10. 获取出错误号的信息 在string.h中char* strerror(int errnum)来获得这个错误号对应的错误信息
    11. 类比进程,子进程结束父进程有责任wait or waitpid去回收子进程的PCB资源 如果不回收子进程将变为僵尸进程
    12. 子线程结束后不会收将会产生僵尸线程
    13. 子线程不需要一定要主线程(main线程)去回收 任何线程都可以去回收其他已经结束的线程
    14. 注意线程pthread库并非linux gcc自带库 libpthread.so 在编译时需要指定-pthread
    15. gcc learn_pthread_create.c -0 create -pthread
    16. gcc learn_pthread_create.c -0 create -l pthread
    17. 都对
    18. */
    19. #include <stdio.h>
    20. #include <pthread.h>
    21. #include <string.h>
    22. #include <unistd.h>
    23. void *callback(void *arg);
    24. int main()
    25. {
    26. // 创建一个子线程
    27. pthread_t tid;
    28. int num = 10; //可以搞个结构体将函数的参数全部放在那个结构体里 再往回调函数内传
    29. int ret = pthread_create(&tid, NULL, callback, (void *)&num);
    30. if (ret != 0)
    31. {
    32. printf("%s\n", strerror(ret));
    33. }
    34. for (int i = 0; i < 5; i++)
    35. {
    36. printf("parent thread\n");
    37. }
    38. int *t_return;
    39. //返回值是二级指针 是因为callback的返回值类型被规定为一级指针void*
    40. pthread_join(tid, (void **)&t_return); //阻塞回收子线程资源
    41. //子线程的返回值保存在 t_return中
    42. printf("son thread return %d\n", *t_return);
    43. pthread_exit(NULL); //让子线程退出 当主线程退出时 不会影响其他正常运行的线程
    44. //主线程结束 pthread_exit下面的代码是不会执行的 即不会打印main thread exsit
    45. printf("main thread exsit\n"); //不会执行
    46. //当主线程return 相当于进程exit所有子线程全部结束!!!
    47. return 0; //不会执行
    48. }
    49. void *callback(void *arg)
    50. {
    51. printf("child thread\n");
    52. printf("arg value %d\n", *(int *)arg);
    53. pthread_t id = pthread_self();
    54. printf("id: %ld\n", id);
    55. // int val = 100; //局部变量 返回的还是这个变量的地址
    56. //主线程拿去的时候这个变量已经销毁了(子线程栈释放了) 主线程得到的是野内存随机值
    57. static int val = 100; //全局存储区
    58. //通过pthread_exit结束子进程 并返回值
    59. pthread_exit((void *)&val); //与return (void *)&val是一样的!!!!!! 返回一个局部变量是没有意义的
    60. // return NULL; //相当于pthread_exit(NULL)
    61. }

    线程的分离

    1. /*
    2. #include <pthread.h>
    3. int pthread_detach(pthread_t thread);
    4. 标记传入的线程thread为detach分离状态
    5. 当分离状态的线程终止 其资源会自动释放还给系统 不再需要其他线程join来回收此线程的资源!!!!
    6. 当去分离一个已经是分离状态的线程将会导致位未知的结果 不能去连接(join)一个已经分离(detach)的线程
    7. 成功返回0 失败返回strerror的错误号
    8. 注意线程pthread库并非linux gcc自带库 libpthread.so 在编译时需要指定-pthread
    9. gcc learn_pthread_create.c -0 create -pthread
    10. gcc learn_pthread_create.c -0 create -l pthread
    11. 都对
    12. */
    13. #include <stdio.h>
    14. #include <pthread.h>
    15. #include <string.h>
    16. #include <unistd.h>
    17. void *
    18. callback(void *arg);
    19. int main()
    20. {
    21. // 创建一个子线程
    22. pthread_t tid;
    23. int num = 10; //可以搞个结构体将函数的参数全部放在那个结构体里 再往回调函数内传
    24. int ret = pthread_create(&tid, NULL, callback, (void *)&num);
    25. if (ret != 0)
    26. {
    27. printf("%s\n", strerror(ret));
    28. }
    29. for (int i = 0; i < 5; i++)
    30. {
    31. printf("parent thread\n");
    32. }
    33. pthread_detach(tid); //设置子线程分离 当分离状态的线程终止 其资源会自动释放还给系统 不再需要其他线程join来回收此线程的资源!!!!
    34. pthread_exit(NULL); //让子线程退出 当主线程退出时 不会影响其他正常运行的线程
    35. //主线程结束 pthread_exit下面的代码是不会执行的 即不会打印main thread exsit
    36. printf("main thread exsit\n"); //不会执行
    37. //当主线程return 相当于进程exit所有子线程全部结束!!!
    38. return 0; //不会执行
    39. }
    40. void *callback(void *arg)
    41. {
    42. printf("child thread\n");
    43. printf("arg value %d\n", *(int *)arg);
    44. pthread_t id = pthread_self();
    45. printf("id: %ld\n", id);
    46. // int val = 100; //局部变量 返回的还是这个变量的地址
    47. //主线程拿去的时候这个变量已经销毁了(子线程栈释放了) 主线程得到的是野内存随机值
    48. static int val = 100; //全局存储区
    49. //通过pthread_exit结束子进程 并返回值
    50. pthread_exit((void *)&val); //与return (void *)&val是一样的!!!!!! 返回一个局部变量是没有意义的
    51. // return NULL; //相当于pthread_exit(NULL)
    52. }

    线程取消

    1. /*
    2. #include <pthread.h>
    3. int pthread_cancel(pthread_t thread);
    4. 给传入的thread号线程发送取消请求(终止线程)
    5. 对应线程是否以及合适终止 取决于对应线程的两个参数 他的cancelabilityd的state以及type
    6. 在pthread_create创建线程的第二个参数可以传入一个结构体来设置这两个属性
    7. 系统并不能保证在调用pthread_cancel后立即取消线程
    8. 只有当线程运行到cancellation point取消点函数 线程会才会取消!!!!!!!
    9. 取消点函数可以在man pthreads中查看有很多 一般是系统调用 所以我们可以粗略地人为一般的系统调用是取消点函数
    10. 如果子线程中没有取消点的话,子线程会一直运行到结束(return or pthread_exit)
    11. 或者可以通过 pthread_setcanceltype
    12. 成功返回0 失败返回strerror的错误号
    13. 注意线程pthread库并非linux gcc自带库 libpthread.so 在编译时需要指定-pthread
    14. gcc learn_pthread_create.c -0 create -pthread
    15. gcc learn_pthread_create.c -0 create -l pthread
    16. 都对
    17. */
    18. #include <stdio.h>
    19. #include <pthread.h>
    20. #include <string.h>
    21. #include <unistd.h>
    22. void *
    23. callback(void *arg);
    24. int main()
    25. {
    26. // 创建一个子线程
    27. pthread_t tid;
    28. int num = 10; //可以搞个结构体将函数的参数全部放在那个结构体里 再往回调函数内传
    29. int ret = pthread_create(&tid, NULL, callback, (void *)&num);
    30. if (ret != 0)
    31. {
    32. printf("%s\n", strerror(ret));
    33. }
    34. //取消子线程
    35. pthread_cancel(tid); //子线程中的printf是取消点
    36. for (int i = 0; i < 5; i++)
    37. {
    38. printf("parent thread\n");
    39. }
    40. pthread_exit(NULL); //让子线程退出 当主线程退出时 不会影响其他正常运行的线程
    41. //主线程结束 pthread_exit下面的代码是不会执行的 即不会打印main thread exsit
    42. printf("main thread exsit\n"); //不会执行
    43. //当主线程return 相当于进程exit所有子线程全部结束!!!
    44. return 0; //不会执行
    45. }
    46. void *callback(void *arg)
    47. {
    48. printf("child thread\n");
    49. printf("arg value %d\n", *(int *)arg);
    50. pthread_t id = pthread_self();
    51. printf("id: %ld\n", id);
    52. // int val = 100; //局部变量 返回的还是这个变量的地址
    53. //主线程拿去的时候这个变量已经销毁了(子线程栈释放了) 主线程得到的是野内存随机值
    54. static int val = 100; //全局存储区
    55. //通过pthread_exit结束子进程 并返回值
    56. pthread_exit((void *)&val); //与return (void *)&val是一样的!!!!!! 返回一个局部变量是没有意义的
    57. // return NULL; //相当于pthread_exit(NULL)
    58. }

    线程属性

    pthreadcreate的第二个参数pthread_attr_t* attr 设置线程属性
    pthread_attr_t为线程属性类型
    man pthread_attr
    加两次tab可以看到所有attr相关函数
    int pthread_attr_init(pthread_attr_t attr);
    int pthread_attr_destroy(pthread_attr_t
    attr);
    int pthread_attr_getdetachstate(const pthread_attr_t attr,int detachstate);
    int pthread_attr_setdetachstate(pthread_attr_t attr,int detachstate);
    线程栈的相关设置函数
    pthread_attr_setstack pthread_attr_setstackaddr pthread_attr_setstacksize

    1. /*
    2. int pthread_attr_init(pthread_attr_t* attr);
    3. 初始化线程属性变量
    4. 线程属性不止一些标志位还有一些复杂的变量 所以在pthread_attr_t创建出来后
    5. 不能简单地 显式地设置一些属性 需要调用init函数来将这个变量清空
    6. int pthread_attr_destroy(pthread_attr_t* attr);
    7. 释放线程属性的资源
    8. int pthread_attr_getdetachstate(const pthread_attr_t* attr,int *detachstate);
    9. 获取线程分离状态的属性
    10. detachstate为传出参数
    11. 创建线程时可以直接指定线程是detached状态还是joinable状态(其实就是不分离的状态)
    12. PTHREAD_CREATE_DETACHED 线程结束后不需要其他线程去join回收资源 系统会回收这个线程的资源
    13. PTHREAD_CREATE_JOINABLE(默认的线程的状态)
    14. int pthread_attr_setdetachstate(pthread_attr_t* attr,int *detachstate);
    15. 设置线程分离状态的属性
    16. int pthread_attr_getstacksize(const pthread_attr_t* attr,size_t *stacksize);
    17. stacksize为传出参数是线程栈的大小
    18. */
    19. #include <stdio.h>
    20. #include <pthread.h>
    21. #include <string.h>
    22. #include <unistd.h>
    23. void *
    24. callback(void *arg);
    25. int main()
    26. {
    27. //创建线程属性变量
    28. pthread_attr_t attr;
    29. //线程属性不止一些标志位还有一些复杂的变量 所以在pthread_attr_t创建出来后
    30. //不能简单地 显式地设置一些属性 需要调用init函数来将这个变量清空
    31. pthread_attr_init(&attr);
    32. //设置线程创建后的分离状态的属性
    33. pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); //使用attr创建后就是分离状态
    34. //获取线程栈的大小(因为我们没有设置过线程栈的大小 所以这里获取到的是线程栈的默认大小)
    35. size_t *size;
    36. pthread_attr_getstacksize(&attr, &size);
    37. printf("thread default stacksize is %ld\n", size); //8388608B = 8MB 默认线程栈大小为8MB
    38. // 创建一个子线程
    39. pthread_t tid;
    40. int num = 10; //可以搞个结构体将函数的参数全部放在那个结构体里 再往回调函数内传
    41. int ret = pthread_create(&tid, &attr, callback, (void *)&num);
    42. if (ret != 0)
    43. {
    44. printf("%s\n", strerror(ret));
    45. }
    46. for (int i = 0; i < 5; i++)
    47. {
    48. printf("parent thread\n");
    49. }
    50. //释放子线程属性资源
    51. pthread_attr_destroy(&attr);
    52. pthread_exit(NULL); //让子线程退出 当主线程退出时 不会影响其他正常运行的线程
    53. //主线程结束 pthread_exit下面的代码是不会执行的 即不会打印main thread exsit
    54. printf("main thread exsit\n"); //不会执行
    55. //当主线程return 相当于进程exit所有子线程全部结束!!!
    56. return 0; //不会执行
    57. }
    58. void *callback(void *arg)
    59. {
    60. printf("child thread\n");
    61. printf("arg value %d\n", *(int *)arg);
    62. pthread_t id = pthread_self();
    63. printf("id: %ld\n", id);
    64. // int val = 100; //局部变量 返回的还是这个变量的地址
    65. //主线程拿去的时候这个变量已经销毁了(子线程栈释放了) 主线程得到的是野内存随机值
    66. static int val = 100; //全局存储区
    67. //通过pthread_exit结束子进程 并返回值
    68. pthread_exit((void *)&val); //与return (void *)&val是一样的!!!!!! 返回一个局部变量是没有意义的
    69. // return NULL; //相当于pthread_exit(NULL)
    70. }