建站提交历史文章,原文写作时间 2023 年 2 月前后。

线程基本操作

  • 编译包含线程函数的程序,需要导入动态库-l pthread。(部分版本会自动导入)

查看线程

查看进程线程

1
$ ps -Lf <pid>  // 查看 pid 进程线程

获取当前线程号

1
2
3
4
5
#include <pthread.h>
// get the thread identifier
// return value:
// pthread identifier, always succeed
pthread_t pthread_self(void);

比较线程号

1
2
3
4
5
6
7
8
9
#include <pthread.h>
// compare thread identifier
// t1:
// thread identifier 1
// t2:
// thread identifier 2
// return value:
// return nonzero value if there are equal, otherwise return 0
int pthread_equal(pthread_t t1, pthread_t t2);

线程创建

  • 回调函数传入参数必须是一个任意指针,返回值也是任意指针。之所以是任意指针,是因为C语言没有类型模板的语法,而任意指针可以指向任意数据类型。这意味着你既可以使用基本数据类型传入任意单一参数,也可以使用结构体传入多个参数;返回值同理。
  • 返回值:错误时返回错误号errorno),这与多数函数都不相同,大多数pthread系列函数都有这个特征。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <pthread.h>
// create a thread
// thread:
// to receive the tid
// pthread_t is long int in most OS, but it's structure in some OS
// attr:
// a structure to determine the attributes for new thread
// NULL for using the default attributes
// unually NULL is enough
// start_routine:
// the callback function the thread start in
// arg:
// the arguments passing to the "start_routine"
// return value:
// return 0 for success, or errorno on error
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

// to print the error information while failed
#include <stdio.h>
#include <string.h>
printf("%s: %s\n", "pthread_create", strerror(errorno));

// About more
// $ man 3 pthread_create

线程退出

  • 线程退出与回调函数中的return一致,回调函数中的return即调用了pthread_exit函数退出,其中的retval可以在线程回收中被接收。
  • main函数即为主线程所在的函数,main函数中的return为进程退出,与pthread_exit不一致,因此pthread_exit通常在主线程中使用。进程退出与线程退出的不同在于:进程退出将立即结束所有线程;仅主线程退出,子线程依然可以运行到结束;所有线程退出后,进程自动退出。
1
2
3
4
5
6
7
8
#include <pthread.h>
// terminate the calling thread
// retval:
// the exit value, can be received by "join"
void pthread_exit(void *retval);

// About more
// $ man 3 pthread_exit

线程回收

join 阻塞回收线程

  • 线程连接

  • 接收线程 返回值时需要注意,返回值不应存储于栈空间栈空间线程(即函数体)生命周期结束后即释放,如果返回值存储在栈空间,将出现未定义的行为。

  • 为避免返回值存储在栈空间,可以使用静态变量全局变量。在传递字符串数组时需特别注意二级指针

1
2
3
4
5
6
7
8
9
10
11
12
#include <pthread.h>
// blocked and waiting the termination of the thread
// thread:
// thread id to join
// retval:
// receive the return value of the thread
// return value:
// return 0 for success, or errorno on error
int pthread_join(pthread_t thread, void **retval);

// About more:
// $ man 3 pthread_join

注:由于join直译为连接,所以该线程回收又称为线程连接。

detach 分离线程

  • 线程分离后,线程结束将自动释放资源而无需主线程回收。一个子线程只能进行一次线程分离或线程连接,否则将出现未定义的错误。
1
2
3
4
5
6
7
8
9
10
#include <pthread.h>
// detach the thread and the thread will release the resources automatically
// thread:
// thread id to detach
// return value:
// return 0 for success, or errorno on error
int pthread_detach(pthread_t thread);

// About more
// $ man 3 pthread_detach

join 与 detach 的关系

  • joindetach的区别在于:join将阻塞地等待线程结束,并回收子线程结束状态;detach将非阻塞地脱离线程线程结束后将自动释放资源而无需等待主线程回收资源;如果子线程未被joindetach任意一种处理,将产生僵尸线程直至主线程结束。
  • 与进程对比,joinwait相仿,将阻塞地等待子进程结束;而detach则是将子进程托管给init进程,进程结束将立即释放资源。

示例代码

  • 此处回调函数的返回值为char **类型,指向字符串指针。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void *func(void *arg) {
printf("%s\n", *(char **)arg);
printf("Child thread id: %ld\n", pthread_self());
static char **ret;
ret = (char **)malloc(sizeof(char *));
*ret = (char *)malloc(sizeof(char) * 256);
strcpy(*ret, "Child thread exits successfully.");

return (void *)ret;
}

int main() {
pthread_t tid;
char *s = "Hello, this is child thread.";
pthread_create(&tid, NULL, func, &s);

char **ret;
pthread_join(tid, (void **)&ret);
printf("%s\n", *ret);

printf("Hello, this is main thread.\n");
printf("Child thread id: %ld, main thread id: %ld\n", tid, pthread_self());

pthread_exit(NULL);

return 0;
}

线程取消

  • 线程取消将向目标线程发送线程终止请求。线程是否终止与线程启动参数有关,默认线程启动支持线程取消。线程取消并不立即发生,需要运行到线程取消点,常用接口函数多数都是线程取消点,详见文档。
1
2
3
4
5
6
7
8
9
10
#include <pthread.h>
// send a cancellation request to a thread
// thread:
// thread identifier
// return value:
// return 0 for success, or errorno on error
int pthread_cancel(pthread_t thread);

// About more
// $ man 3 pthread_cancel

线程属性

pthread_attr 函数簇

  • pthread_attr函数簇

  • 命令行键入$ pthread_attr连续按下3Tab,显示pthread_attr函数簇全部函数。

  • pthread_attr_initpthread_attr_destory用于初始化或销毁pthread_attr_t结构体。pthread库要求使用pthread_initpthread_destory操作,而定义与return无法正常初始化与销毁该结构体。使用pthread_attr_init就必须进行pthread_attr_destory

1
2
3
4
5
6
7
8
#include <pthread.h>
// initialize and destory thread attributes structure
// attr:
// the thread attributes structure
// return value:
// return 0 for success, or errorno on error
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);
  • 其他pthread_attr函数簇成员,用于具体修改pthread_attr_t结构体,详见文档。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void *func(void *arg) {
printf("Child thread id: %ld\n", pthread_self());
return NULL;
}

int main() {
pthread_attr_t attr;
pthread_attr_init(&attr); // 初始化参数体

// 任何的 pthread_attr 操作, 这里例如: 分离线程
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

pthread_t tid;
pthread_create(&tid, &attr, func, NULL);

printf("Hello, this is main thread.\n");
printf("Child thread id: %ld, main thread id: %ld\n", tid, pthread_self());

pthread_attr_destroy(&attr); // 销毁参数体
pthread_exit(NULL);

return 0;
}