文章目录
创建线程
POSIX标准线程接口需要引入<pthread.h>
头文件。
如果通过gcc进行编辑的话,需要连接pthread库:gcc ... -lpthread
创建线程函数概述
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
pthread_t *thread
:该参数是POSIX定义的线程id,用于创建线程后获取线程id。const pthread_attr_t *attr
:线程有很多属性用来设置该线程的一些行为与特性,通过该参数用来定义。当传入NULL则使用缺省设置。通常没有特别要求,使用缺省即可。void *(*start_routine)(void *)
:一个函数指针。当一个新的线程被创建后,内核会调用该函数进行线程过程的执行。void *arg
:需要传入线程过程函数的入参,由内核在调用过程函数时传入。
创建线程代码示例
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
/**
* 函数执行体。类似java中的Thread#run函数。
* 当函数体执行完毕,则线程结束。
* @param arg 创建线程时传入的参数。
* @return 线程返回的结果,可以被创建该线程的父线程通过pthread_join获取。
*/
void *thread_proc(void *arg) {
for (int i = 0; i < 100; ++i) {
usleep(1000 * 5);
printf("%lld", (long long) arg); //打印传入的数字
}
return NULL;
}
int main(void) {
setbuf(stdout, NULL);
for (long long i = 1; i <= 3; ++i) {//创建多个线程
pthread_t tid; //线程id
int errno = pthread_create(&tid, NULL, thread_proc, (void *) i); //创建线程,并传入一个数字
if (errno) { //当线程创建失败,返回的errno非0
fprintf(stderr, "pthread_create:%s\n", strerror(errno)); //打印异常信息
return -1;
}
}
getchar();//防止主线程退出,回车即退出。
return 0;
}
运行可发现多个线程同时打印数字。
获取线程ID
获取线程id函数概述
pthread_t pthread_self(void);
返回当前正在调用的线程id。
需要注意的是,返回的pthread_t
是POSIX定义的线程id,并不是实际系统内核的线程ID。如果需要获取系统内核的线程ID,则需要引入<sys/syscall.h>
头文件,并通过系统调用syscall(SYS_gettid)
来获取。
获取线程id代码示例
#include <pthread.h>
#include <stdio.h>
#include <string.h>
/**
* 函数执行体。类似java中的Thread#run函数。
* 当函数体执行完毕,则线程结束。
* @param arg 创建线程时传入的参数。
* @return 线程返回的结果,可以被创建该线程的父线程通过pthread_join获取。
*/
void *thread_proc(void *arg) {
pthread_t pt = pthread_self();//获取当前线程id
printf("%lu线程:线程过程函数被调用\n", (unsigned long) pt);
return NULL;
}
int main(void) {
pthread_t tid; //线程id
int errno = pthread_create(&tid, NULL, thread_proc, NULL); //创建线程
if (errno) { //当线程创建失败,返回的errno非0
fprintf(stderr, "pthread_create:%s\n", strerror(errno)); //打印异常信息
return -1;
}
printf("%lu线程:创建了子线程%lu\n", (unsigned long) pthread_self(), (unsigned long) tid);
getchar();
return 0;
}
汇合线程
汇合线程场景:
- 同步子线程,等待子线程结束
- 获取线程过程函数的返回值
- 清理并释放子线程的残余资源。(有点类似僵尸进程,线程结束后,依然会有返回值和线程id没有被释放,所以线程结束后需要回收。"分离线程"会自动回收。)
所谓汇合线程,实质是父线程等待子线程结束并获取子线程返回的结果的操作。
常用于需要提高程序性能而通过创建子线程来并发执行某些操作,然后父线程获取操作结果的场景。
类似java中的Thread#join()
函数。
汇合线程函数概述
int pthread_join(pthread_t thread, void **value_ptr);
pthread_t thread
:线程id。void **value_ptr
:用于获取线程返回值的指针。
该函数如果被调用,除非目标线程过程函数已终止,否则将暂停调用线程的执行,直到目标线程终止。
汇合线程代码示例
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
/**
* 函数执行体。类似java中的Thread#run函数。
* 当函数体执行完毕,则线程结束。
* @param arg 创建线程时传入的参数。
* @return 线程返回的结果,可以被创建该线程的父线程通过pthread_join获取。
*/
void *thread_proc(void *arg) {
for (int i = 0; i < 100; ++i) {
usleep(1000 * 5);
printf("%lld", (long long) arg);
}
return arg;
}
int main(void) {
setbuf(stdout, NULL);
for (long long i = 1; i <= 3; ++i) {//创建多个线程
pthread_t tid; //线程id
int errno = pthread_create(&tid, NULL, thread_proc, (void *) i); //创建线程,并传入一个数字
if (errno) { //当线程创建失败,返回的errno非0
fprintf(stderr, "pthread_create:%s\n", strerror(errno)); //打印异常信息
return -1;
}
void *val;
errno = pthread_join(tid, &val); //汇合线程,等待传入tid对应的线程执行结束
if (errno) { //当线程汇合失败,返回的errno非0
fprintf(stderr, "pthread_join:%s\n", strerror(errno)); //打印异常信息
return -1;
}
printf("线程%lu返回值:%lld\n", (unsigned long) tid, (long long) val);
}
getchar();//防止主线程退出,回车即退出。
return 0;
}
分离线程
如果一个线程被设置为分离线程,那么该线程不能够也不需要被汇合,它终止后所参与的资源会被系统自动回收。因此在不需要等待线程结束,并且不关心线程过程函数返回值的场景,使用分离线程是最好的方式。
分离线程不允许被汇合,也就意味着,不能通过pthread_join
来试图等待一个分离线程返回。
第一种方式:后标记分离线程
int pthread_detach(pthread_t thread);
该方式,先通过pthread_create
创建一个线程,然后通过pthread_detach
函数来设置该线程为分离线程。如果创建时采用的缺省属性,则默认为可汇合的线程。可汇合线程就意味着可以通过pthread_join
函数来调用,并获取返回值。由于线程过程函数什么时间执行完毕是不确定的,所以有可能在调用pthread_detach
函数时,线程已经结束了,这就没有任何意义了,所以建议采用下文说的第二种方式:创建即分离线程。
代码示例:
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
/**
* 函数执行体。类似java中的Thread#run函数。
* 当函数体执行完毕,则线程结束。
* @param arg 创建线程时传入的参数。
* @return 线程返回的结果,可以被创建该线程的父线程通过pthread_join获取。
*/
void *thread_proc(void *arg) {
for (int i = 0; i < 100; ++i) {
usleep(1000 * 5);
printf("%lld", (long long) arg);
}
return arg;
}
int main(void) {
setbuf(stdout, NULL);
for (long long i = 1; i <= 3; ++i) {//创建多个线程
pthread_t tid; //线程id
int errno = pthread_create(&tid, NULL, thread_proc, (void *) i); //创建线程,并传入一个数字
if (errno) { //当线程创建失败,返回的errno非0
fprintf(stderr, "pthread_create:%s\n", strerror(errno)); //打印异常信息
return -1;
}
errno = pthread_detach(tid);//**分离线程**
if (errno) { //当线程分离失败,返回的errno非0
fprintf(stderr, "pthread_detach:%s\n", strerror(errno)); //打印异常信息
return -1;
}
}
printf("主线程执行完毕。\n");
getchar();//防止主线程退出,回车即退出。
return 0;
}
第二种方式:创建即分离线程
该方式,在通过pthread_create
创建线程时,设置其线程属性,使其创建线程即为分离线程。该方式避免了通过pthread_detach
函数设置的不确定性。
pthread_create函数的第二个参数为线程属性,当传入NULL时,则采用缺省属性。要创建分离线程,需要定义线程属性并传入:
pthread_attr_t attr;
定义线程属性变量。pthread_attr_init(&attr);
初始化线程属性,设置缺省值。pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
设置分离线程状态。pthread_attr_destroy(&attr);
销毁线程属性中的动态资源。属性用完后,要销毁,避免内存泄漏。
代码示例
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
/**
* 函数执行体。类似java中的Thread#run函数。
* 当函数体执行完毕,则线程结束。
* @param arg 创建线程时传入的参数。
* @return 线程返回的结果,可以被创建该线程的父线程通过pthread_join获取。
*/
void *thread_proc(void *arg) {
for (int i = 0; i < 100; ++i) {
usleep(1000 * 5);
printf("%lld", (long long) arg);
}
return arg;
}
int main(void) {
setbuf(stdout, NULL);
pthread_t tid; //线程id
pthread_attr_t attr; //定义线程属性变量
pthread_attr_init(&attr); //初始化线程属性
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED); //设置分离线程状态
for (long long i = 1; i <= 3; ++i) {//创建多个线程
int errno = pthread_create(&tid, &attr, thread_proc, (void *) i); //创建线程,并传入一个数字
if (errno) { //当线程创建失败,返回的errno非0
fprintf(stderr, "pthread_create:%s\n", strerror(errno)); //打印异常信息
return -1;
}
}
pthread_attr_destroy(&attr);//销毁线程属性中的动态资源
printf("主线程执行完毕。\n");
getchar();//防止主线程退出,回车即退出。
return 0;
}
终止线程
当线程过程函数执行完毕,线程即终止。
当线程过程函数在内部的调用逻辑过程中,需要再任意地方需要结束当前线程,可调用pthread_exit
函数来结束。
void pthread_exit(void *value_ptr);
传入的void *value_ptr
与线程return的值是一个意思,外部通过pthread_join获取的线程值,如果通过pthread_exit返回的,即该值;如果通过return返回的,即return的值。
代码示例
thread_proc不再return,直接通过pthread_exit返回。
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
/**
* 函数执行体。类似java中的Thread#run函数。
* 当函数体执行完毕,则线程结束。
* @param arg 创建线程时传入的参数。
* @return 线程返回的结果,可以被创建该线程的父线程通过pthread_join获取。
*/
void *thread_proc(void *arg) {
for (int i = 0; i < 100; ++i) {
usleep(1000 * 5);
printf("%lld", (long long) arg);
}
pthread_exit(arg);//结束线程
// return arg;
}
int main(void) {
setbuf(stdout, NULL);
for (long long i = 1; i <= 3; ++i) {//创建多个线程
pthread_t tid; //线程id
int errno = pthread_create(&tid, NULL, thread_proc, (void *) i); //创建线程,并传入一个数字
if (errno) { //当线程创建失败,返回的errno非0
fprintf(stderr, "pthread_create:%s\n", strerror(errno)); //打印异常信息
return -1;
}
void *val;
errno = pthread_join(tid, &val); //汇合线程,等待传入tid对应的线程执行结束
if (errno) { //当线程汇合失败,返回的errno非0
fprintf(stderr, "pthread_join:%s\n", strerror(errno)); //打印异常信息
return -1;
}
printf("线程%lu返回值:%lld\n", (unsigned long) tid, (long long) val);
}
getchar();//防止主线程退出,回车即退出。
return 0;
}
线程的比较
关于线程的比较有一个点需要说明的是,POSIX标准提供的线程ID为pthread_t类型的结构体,不是内核系统本身的线程ID,我们一般建议使用POSIX的API,来让我们写的代码具有系统移植性。
那么当我们需要通过pthread_self函数返回的id与某个pthread_t来比较时,也是如此。POSIX提供了一个pthread_equal函数来做两个线程id之间的比较。
为何采用pthread_equal而不直接采用==进行比较,主要也是考虑到pthread_t可能在不同的操作系统,结构体不一样,所以采用pthread_equal就可以屏蔽掉操作系统不同带来的问题。
int pthread_equal(pthread_t t1, pthread_t t2)
当t1与t2相等时,返回非0;不相等时,返回0。