C/C++ Pthread线程
线程按照其调度者可以分为用户级线程和核心级线程两种
用户级线程主要解决的是上下文切换的问题,它的调度算法和调度过程全部由用户自行选择决定,在运行时不需要特定的内核支持;
我们常用基本就是用户级线程,所以就只总结一下POSIX提供的用户级线程接口;
基本线程操作相关的函数:
1线程的建立结束
2线程的互斥和同步
3使用信号量控制线程
4线程的基本属性配置
基本线程操作:
函数 | 说明 |
---|---|
pthread_create() | 创建线程开始运行相关线程函数,运行结束则线程退出 |
pthread_eixt() | 因为exit()是用来结束进程的,所以则需要使用特定结束线程的函数 |
pthread_join() | 挂起当前线程,用于阻塞式地等待线程结束,如果线程已结束则立即返回,0=成功 |
pthread_cancel() | 发送终止信号给thread线程,成功返回0,但是成功并不意味着thread会终止 |
pthread_testcancel() | 在不包含取消点,但是又需要取消点的地方创建一个取消点,以便在一个没有包含取消点的执行代码线程中响应取消请求. |
pthread_setcancelstate() | 设置本线程对Cancel信号的反应 |
pthread_setcanceltype() | 设置取消状态 继续运行至下一个取消点再退出或者是立即执行取消动作 |
pthread_setcancel() | 设置取消状态 |
互斥与同步机制基本函数
函数 | 说明 |
---|---|
pthread_mutex_init() | 互斥锁的初始化 |
pthread_mutex_lock() | 锁定互斥锁,如果尝试锁定已经被上锁的互斥锁则阻塞至可用为止 |
pthread_mutex_trylock() | 非阻塞的锁定互斥锁 |
pthread_mutex_unlock() | 释放互斥锁 |
pthread_mutex_destory() | 互斥锁销毁函数 |
信号量线程控制(默认无名信号量)
函数 | 说明 |
---|---|
sem_init(sem) | 初始化一个定位在sem的匿名信号量 |
sem_wait() | 把信号量减1操作,如果信号量的当前值为0则进入阻塞,为原子操作 |
sem_trywait() | 如果信号量的当前值为0则返回错误而不是阻塞调用(errno=EAGAIN),其实是sem_wait()的非阻塞版本 |
sem_post() | 给信号量的值加1,它是一个“原子操作”,即同时对同一个信号量做加1,操作的两个线程是不会冲突的 |
sem_getvalue(sval) | 把sem指向的信号量当前值放置在sval指向的整数上 |
sem_destory(sem) | 销毁由sem指向的匿名信号量 |
线程属性配置相关函数
函数 | 说明 |
---|---|
pthread_attr_init() | 初始化配置一个线程对象的属性,需要用pthread_attr_destroy函数去除已有属性 |
pthread_attr_setscope() | 设置线程属性 |
pthread_attr_setschedparam() | 设置线程优先级 |
pthread_attr_getschedparam() | 获取线程优先级 |
基本的线程建立运行pthread_create
/* thread.c */ #include <stdio.h> #include <stdlib.h> #include <pthread.h> #define THREAD_NUMBER 3 /*线程数*/ #define REPEAT_NUMBER 5 /*每个线程中的小任务数*/ #define DELAY_TIME_LEVELS 10.0 /*小任务之间的最大时间间隔*/ // void *thrd_func(void *arg) { /* 线程函数例程 */ int thrd_num = (int)arg; int delay_time = 0; int count = 0; printf("Thread %d is starting\n", thrd_num); for (count = 0; count < REPEAT_NUMBER; count++) { delay_time = (int)(rand() * DELAY_TIME_LEVELS/(RAND_MAX)) + 1; sleep(delay_time); printf("\tThread %d: job %d delay = %d\n", thrd_num, count, delay_time); } printf("Thread %d finished\n", thrd_num); pthread_exit(NULL); } int main(void) { pthread_t thread[THREAD_NUMBER]; int no = 0, res; void * thrd_ret; srand(time(NULL)); for (no = 0; no < THREAD_NUMBER; no++) { /* 创建多线程 */ res = pthread_create(&thread[no], NULL, thrd_func, (void*)no); if (res != 0) { printf("Create thread %d failed\n", no); exit(res); } } printf("Create treads success\n Waiting for threads to finish...\n"); for (no = 0; no < THREAD_NUMBER; no++) { /* 等待线程结束 */ res = pthread_join(thread[no], &thrd_ret); if (!res) { printf("Thread %d joined\n", no); } else { printf("Thread %d join failed\n", no); } } return 0; }
例程中循环3次建立3条线程,并且使用pthread_join函数依次等待线程结束;
线程中使用rand()获取随机值随机休眠5次,随意会出现后执行的线程先执行完成;
运行结果:
$ gcc thread.c -lpthread $ ./a.out Create treads success Waiting for threads to finish... Thread 0 is starting Thread 1 is starting Thread 2 is starting Thread 1: job 0 delay = 2 Thread 1: job 1 delay = 2 Thread 0: job 0 delay = 8 Thread 2: job 0 delay = 10 Thread 2: job 1 delay = 3 Thread 1: job 2 delay = 10 Thread 0: job 1 delay = 8 Thread 0: job 2 delay = 3 Thread 0: job 3 delay = 1 Thread 2: job 2 delay = 8 Thread 1: job 3 delay = 8 Thread 1: job 4 delay = 1 Thread 1 finished Thread 2: job 3 delay = 6 Thread 0: job 4 delay = 7 Thread 0 finished Thread 0 joined Thread 1 joined Thread 2: job 4 delay = 10 Thread 2 finished Thread 2 joined
可以看到,线程1先于线程0执行,但是pthread_join的调用时间顺序,先等待线程0执行;
由于线程1已经早结束,所以线程0被pthread_join等到的时候,线程1已结束,就在等待到线程1时,直接返回;
线程执行的互斥和同步pthread_mutex_lock
在上面的程序中增加互斥锁
/*thread_mutex.c*/ #include <stdio.h> #include <stdlib.h> #include <pthread.h> #define THREAD_NUMBER 3 /* 线程数 */ #define REPEAT_NUMBER 3 /* 每个线程的小任务数 */ #define DELAY_TIME_LEVELS 10.0 /*小任务之间的最大时间间隔*/ pthread_mutex_t mutex; void *thrd_func(void *arg) { int thrd_num = (int)arg; int delay_time = 0, count = 0; int res; /* 互斥锁上锁 */ res = pthread_mutex_lock(&mutex); if (res) { printf("Thread %d lock failed\n", thrd_num); pthread_exit(NULL); } printf("Thread %d is starting\n", thrd_num); for (count = 0; count < REPEAT_NUMBER; count++) { delay_time = (int)(rand() * DELAY_TIME_LEVELS/(RAND_MAX)) + 1; sleep(delay_time); printf("\tThread %d: job %d delay = %d\n", thrd_num, count, delay_time); } printf("Thread %d finished\n", thrd_num); pthread_exit(NULL); } int main(void) { pthread_t thread[THREAD_NUMBER]; int no = 0, res; void * thrd_ret; srand(time(NULL)); /* 互斥锁初始化 */ pthread_mutex_init(&mutex, NULL); for (no = 0; no < THREAD_NUMBER; no++) { res = pthread_create(&thread[no], NULL, thrd_func, (void*)no); if (res != 0) { printf("Create thread %d failed\n", no); exit(res); } } printf("Create treads success\n Waiting for threads to finish...\n"); for (no = 0; no < THREAD_NUMBER; no++) { res = pthread_join(thread[no], &thrd_ret); if (!res) { printf("Thread %d joined\n", no); } else { printf("Thread %d join failed\n", no); } } /****互斥锁解锁***/ pthread_mutex_unlock(&mutex); pthread_mutex_destroy(&mutex); return 0; }
在上面的例程中直接添加同步锁pthread_mutex_t;
在线程中加入,于是程序在执行线程程序时;
调用pthread_mutex_lock上锁,发现上锁时候后进入等待,等待锁再次释放后重新上锁;
所以线程程序加载到队列中等待,等待成功上锁后继续执行程序代码;
运行结果
$gcc thread_mutex.c -lpthread $ ./a.out Create treads success Waiting for threads to finish... Thread 0 is starting Thread 0: job 0 delay = 9 Thread 0: job 1 delay = 4 Thread 0: job 2 delay = 7 Thread 0 finished Thread 0 joined Thread 1 is starting Thread 1: job 0 delay = 6 Thread 1: job 1 delay = 4 Thread 1: job 2 delay = 7 Thread 1 finished Thread 1 joined Thread 2 is starting Thread 2: job 0 delay = 3 Thread 2: job 1 delay = 1 Thread 2: job 2 delay = 6 Thread 2 finished Thread 2 joined
跟例程1中执行结果不同,线程程序被加载到队列中而不能马上执行,需要等到能够成功上锁;
上锁后,继续执行线程程序,上锁执行;
这样线程被依次执行的情况在实际使用场景中经常出现;
使用场景:
当用户登录后获取秘钥才能继续获取该用户的基本信息时;需要等待登录线程结束后才能继续执行获取用户信息的线程时,
需要调用两条线程,假如是:threadLogin(),threadGetInfo(); 则可以有2种方法实现:
1 此时可以使用互斥锁同时一次性调用完threadLogin()和threadGetInfo();
2 当然也可以不使用互斥锁直接在threadLogin()中登录验证成功后调用threadGetInfo();
相比之下,方式1更加清晰的显示逻辑关系,增加代码可读性可扩展性。
使用信号量控制线程的执行顺序sem_post
修改上面例程,上面的是使用pthread_mutex_lock互斥锁控制线程执行顺序,
使用另外一种线程执行顺序的控制;
/* thread_sem.c */ #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <semaphore.h> #define THREAD_NUMBER 3 #define REPEAT_NUMBER 3 #define DELAY_TIME_LEVELS 10.0 sem_t sem[THREAD_NUMBER]; void * thrd_func(void *arg) { int thrd_num = (int)arg; int delay_time = 0; int count = 0; sem_wait(&sem[thrd_num]); printf("Thread %d is starting\n", thrd_num); for (count = 0; count < REPEAT_NUMBER; count++) { delay_time = (int)(rand() * DELAY_TIME_LEVELS/(RAND_MAX)) + 1; sleep(delay_time); printf("\tThread %d: job %d delay = %d\n", thrd_num, count, delay_time); } printf("Thread %d finished\n", thrd_num); pthread_exit(NULL); } int main(void) { pthread_t thread[THREAD_NUMBER]; int no = 0, res; void * thrd_ret; srand(time(NULL)); for (no = 0; no < THREAD_NUMBER; no++) { sem_init(&sem[no], 0, 0); res = pthread_create(&thread[no], NULL, thrd_func, (void*)no); if (res != 0) { printf("Create thread %d failed\n", no); exit(res); } } printf("Create treads success\n Waiting for threads to finish...\n"); sem_post(&sem[THREAD_NUMBER - 1]); for (no = THREAD_NUMBER - 1; no >= 0; no--) { res = pthread_join(thread[no], &thrd_ret); if (!res) { printf("Thread %d joined\n", no); } else { printf("Thread %d join failed\n", no); } sem_post(&sem[(no + THREAD_NUMBER - 1) % THREAD_NUMBER]); } for (no = 0; no < THREAD_NUMBER; no++) { sem_destroy(&sem[no]); } return 0; }
执行结果,仍然是建立3条线程,每条线程执行时休眠随机时长:
$ gcc thread_sem.c -lpthread $ ./a.out Create treads success Waiting for threads to finish... Thread 2 is starting Thread 2: job 0 delay = 9 Thread 2: job 1 delay = 9 Thread 2: job 2 delay = 5 Thread 2 finished Thread 2 joined Thread 1 is starting Thread 1: job 0 delay = 5 Thread 1: job 1 delay = 7 Thread 1: job 2 delay = 4 Thread 1 finished Thread 1 joined Thread 0 is starting Thread 0: job 0 delay = 3 Thread 0: job 1 delay = 9 Thread 0: job 2 delay = 8 Thread 0 finished Thread 0 joined
执行结果与第2个例程非常相似,只不过教材中进行倒序执行而已;
那么这种方式其实与使用互斥锁相比,代码量可读性基本持平不相上下;
线程的基本属性pthread_attr_setscope
设置属性一般有:
1 绑定属性
2 分离属性
3 堆栈地址
4 堆栈大小
5 优先级
关于绑定属性就是绑定于内核线程;
分离属性主要是讲线程结束后是否马上释放相应的内存;
/* thread_attr.c */ #include <stdio.h> #include <stdlib.h> #include <pthread.h> #define THREAD_NUMBER 1 #define REPEAT_NUMBER 3 #define DELAY_TIME_LEVELS 10.0 int finish_flag = 0; void * thrd_func(void * arg){ int delay_time = 0; int count = 0; printf("Thread is starting\n"); for (count = 0; count < REPEAT_NUMBER; count++) { delay_time = (int)(rand() * DELAY_TIME_LEVELS/(RAND_MAX)) + 1; sleep(delay_time); printf("\tThread : job %d delay = %d\n", count, delay_time); } printf("Thread finished\n"); finish_flag = 1; pthread_exit(NULL); } int main(void) { pthread_t thread; pthread_attr_t attr; int res = 0; srand(time(NULL)); res = pthread_attr_init(&attr); if (res != 0) { printf("Create attribute failed\n"); exit(res); } res = pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM); res += pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); if (res != 0) { printf("Setting attribute failed\n"); exit(res); } res = pthread_create(&thread, &attr, thrd_func, NULL); if (res != 0) { printf("Create thread failed\n"); exit(res); } pthread_attr_destroy(&attr); printf("Create tread success\n"); while(!finish_flag){ printf("Waiting for thread to finish...\n"); sleep(2); } return 0; }
在运行前后使用 $ free 命令查看内存前后的使用情况发现:
在线程结束后内存马上被释放;
其实,一般线程的属性直接使用系统默认属性即可;
关于线程的使用,大约就是这样。