一、线程相关函数

1.pthread_t

每一个线程都有唯一一个线程ID,ID类型为pthread_t是一个无符号的长整形数,

  1. pthread_t pthread_self(void); // 返回当前线程的线程ID

2.创建线程函数

在一个进程中调用线程创建函数,就可得到一个子线程,和进程不同,需要给每一个创建出的线程指定一个处理函数,否则这个线程无法工作。

  1. int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
  2. void *(*start_routine) (void *), void *arg);

参数:

  • thread: 传出参数,是无符号长整形数,线程创建成功,会将线程 ID 写入到这个指针指向的内存中

  • attr: 线程的属性,一般情况下使用默认属性即可,写 NULL

  • start_routine: 函数指针,创建出的子线程的处理动作,也就是该函数在子线程中执行。

  • arg: 作为实参传递到 start_routine 指针指向的函数内部

返回值:线程创建成功返回 0,创建失败返回对应的错误号

二、线程创建

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. #include <string.h>
  5. #include <pthread.h>
  6. // 子线程的处理代码
  7. void* working(void* arg)
  8. {
  9. printf("我是子线程, 线程ID: %ld\n", pthread_self());
  10. for(int i=0; i<9; ++i)
  11. {
  12. printf("child == i: = %d\n", i);
  13. }
  14. return NULL;
  15. }
  16. int main()
  17. {
  18. // 1. 创建一个子线程
  19. pthread_t tid;
  20. pthread_create(&tid, NULL, working, NULL);
  21. printf("子线程创建成功, 线程ID: %ld\n", tid);
  22. // 2. 子线程不会执行下边的代码, 主线程执行
  23. printf("我是主线程, 线程ID: %ld\n", pthread_self());
  24. for(int i=0; i<3; ++i)
  25. {
  26. printf("i = %d\n", i);
  27. }
  28. // 休息, 休息一会儿...
  29. // sleep(1);
  30. return 0;
  31. }

image.png
可以看到创建子线程后,子线程并没有执行对应的函数

主线程一直在运行,执行期间创建出了子线程,说明主线程有 CPU 时间片,在这个时间片内将代码执行完毕了,主线程就退出了。子线程被创建出来之后需要抢cpu时间片, 抢不到就不能运行,如果主线程退出了, 虚拟地址空间就被释放了, 子线程就一并被销毁了。但是如果某一个子线程退出了, 主线程仍在运行, 虚拟地址空间依旧存在。
在没有人为干预的情况下,虚拟地址空间的生命周期和主线程是一样的,与子线程无关。
目前的解决方案:让子线程执行完毕,主线程再退出,可以在主线程中添加挂起函数 sleep();

三、线程退出

在编写多线程程序的时候,如果想要让线程退出,但是不会导致虚拟地址空间的释放(针对于主线程),我们就可以调用线程库中的线程退出函数,只要调用该函数当前线程就马上退出了,并且不会影响到其他线程的正常运行,不管是在子线程或者主线程中都可以使用。

  1. void pthread_exit(void *retval);

参数:线程退出的时候携带的数据,当前子线程的主线程会得到该数据。如果不需要使用,指定为 NULL

四、线程回收

线程和进程一样,子线程退出的时候其内核资源主要由主线程回收,线程库中提供的线程回收函叫做 pthread_join(),这个函数是一个阻塞函数,如果还有子线程在运行,调用该函数就会阻塞,子线程退出函数解除阻塞进行资源的回收,函数被调用一次,只能回收一个子线程,如果有多个子线程则需要循环进行回收。

// 这是一个阻塞函数, 子线程在运行这个函数就阻塞
// 子线程退出, 函数解除阻塞, 回收对应的子线程资源, 类似于回收进程使用的函数 wait()
int pthread_join(pthread_t thread, void **retval);
参数:

  • thread: 要被回收的子线程的线程 ID
  • retval: 二级指针,指向一级指针的地址,是一个传出参数,这个地址中存储了 pthread_exit () 传递出的数据,如果不需要这个参数,可以指定为 NULL

返回值:线程回收成功返回 0,回收失败返回错误号

4.1回收子线程

在子线程退出的时候可以使用 pthread_exit() 的参数将数据传出,在回收这个子线程的时候可以通过 phread_join() 的第二个参数来接收子线程传递出的数据。接收数据有很多种处理方式,下面来列举几种:

4.1.1使用子线程栈

通过函数 pthread_exit(void retval); 可以得知,子线程退出的时候,需要将数据记录到一块内存中,通过参数传出的是存储数据的内存的地址,而不是具体数据,由因为参数是 void 类型,所有这个万能指针可以指向任意类型的内存地址。先来看第一种方式,将子线程退出数据保存在子线程自己的栈区:

4.2.2 使用全局变量

去看苏老师的文章

4.2.3 使用主线程栈

去看苏老师的文章