序言
之前的文章已经回顾了多线程同步的6种方式,这里就互斥锁和条件变量的配合使用,以及关卡的使用举个栗子。
以牛客网上迅雷的一道面试题为例。
1. 题目
编写一个程序,开启3个线程,这3个线程的ID分别为A、B、C。
每个线程将自己的ID在屏幕上打印10遍,要求输出结果必须按ABC的顺序显示;如:ABCABC….依次递推。
2. 实现思路
[1] 不能只使用互斥锁,因为互斥锁的特点是资源的”无序访问“
[2] 可以使用信号量,三个线程之间形成依次通知的关系,由主线程通知启动。
[3] 使用条件变量,三个线程之间形成相互等待的关系,可用条件变量通知等待的线程,由主线程通知启动。
这里使用条件变量进行实现。
3. 一些线程函数原型
线程创建函数:int pthread_create (pthread_t *tid, const pthread_attr_t * attr, void * (void *), void *arg);
等待线程结束函数:int pthread_join(pthread_t tid, void **ret);
互斥锁初始化函数:int pthread_mutex_init (pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);
互斥锁上锁:int pthread_mutex_lock (pthread_mutex_t *mutex);
条件变量初始化函数: int pthread_cond_init(pthread_cond_t *condition, pthread_condattr_t *condattr);
条件变量等待函数:int pthread_cond_wait(pthread_cond_t *condition, pthread_mutex_t *mutex);
条件变量定时等待函数:int pthread_cond_timedwait(pthread_cond_t *condition, pthread_mutex_t *mutex, const struct timespec *abstime);
条件变量通知函数:int pthread_cond_signal(pthread_cond_t *cond);
条件变量广播函数:int pthread_cond_broadcast(pthread_cond_t *cond);
关卡初始化函数:int pthread_barrier_init( pthread_barrier_t *barrier, const pthread_barrierattr_t *attr, unsigned int count);
关卡等待函数:int pthread_barrier_wait( pthread_barrier_t *barrier);
4. 代码实现 - 使用pthread_join()
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
pthread_t A, B, C; //线程ID
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; //互斥锁,条件变量对资源的互斥访问
pthread_cond_t condition = PTHREAD_COND_INITIALIZER; //条件变量
int flag = 0; //把线程阻塞在条件变量的条件,全局变量
void *printThread1(void *arg);
void *printThread2(void *arg);
void *printThread3(void *arg);
int main()
{
/*动态初始化方式
pthread_mutex_init(&lock, NULL);
pthread_cond_init(&condition, NULL);
*/
//建立三个子线程
int res;
if ((res = pthread_create(&A, NULL, printThread1, NULL)) != 0)
{
printf("线程A创建失败\n");
exit(-1);
}
//条件置位让线程1开始执行
flag = 1;
if ((res = pthread_create(&B, NULL, printThread2, NULL)) != 0)
{
printf("线程B创建失败\n");
exit(-1);
}
if ((res = pthread_create(&C, NULL, printThread3, NULL)) != 0)
{
printf("线程C创建失败\n");
exit(-1);
}
//等待各个子线程结束
pthread_join(A, NULL);
pthread_join(B, NULL);
pthread_join(C, NULL);
//释放内存之前销毁互斥锁和条件变量
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&condition);
return 0;
}
void *printThread1(void *arg)
{
int i = 0;
while (i < 10)
{
pthread_mutex_lock(&lock);
if (flag != 1) //flag == 1时,线程1执行
pthread_cond_wait(&condition, &lock);
printf("A");
flag = 2;
i++;
pthread_cond_signal(&condition);
pthread_mutex_unlock(&lock);
}
return NULL; //或return ((void *)0);
}
void *printThread2(void *arg)
{
int j = 0;
while (j < 10)
{
pthread_mutex_lock(&lock);
if (flag != 2) //flag == 2时,线程2执行
pthread_cond_wait(&condition, &lock);
printf("B");
flag = 3;
j++;
pthread_cond_signal(&condition);
pthread_mutex_unlock(&lock);
}
return NULL;
}
void *printThread3(void *arg)
{
int k = 0;
while (k < 10)
{
pthread_mutex_lock(&lock);
if (flag != 3) //flag == 3时,线程3执行
pthread_cond_wait(&condition, &lock);
printf("C");
flag = 1;
k++;
pthread_cond_signal(&condition);
pthread_mutex_unlock(&lock);
}
return NULL;
}
说明:
[1] 互斥锁和条件变量静态创建,因此可以选择静态初始化(赋值)或者动态初始化(函数)的方式
[2] 之前将条件置位语句flag = 1放在了创建完线程之后,发现有的时候能正常运行输出,有的时候又完全没有反应。问题在于线程创建之后,运行哪一个是随机的,如果不是线程1条件在等待,则不会继续运行。
[3] pthread_join()对不同函数的等待没有先后顺序,所以pthread_join(A, NULL),pthread_join(B, NULL),pthread_join(C, NULL)谁在前谁在后都没有关系。
[4] int i, j, k的定义放到了各个线程中而不是作为全局变量,因为线程运行起来后直至运行结束才会退出,因此可作为局部变量。
4. 代码实现 - 使用关卡Barriers
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
pthread_t A, B, C; //线程ID
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; //互斥锁,条件变量对资源的互斥访问
pthread_cond_t condition = PTHREAD_COND_INITIALIZER; //条件变量
pthread_barrier_t barrier; //关卡
int flag = 0; //把线程阻塞在条件变量的条件,全局变量
void *printThread1(void *arg);
void *printThread2(void *arg);
void *printThread3(void *arg);
int main()
{
//关卡初始化,count = 子线程个数 + 1(主线程)
pthread_barrier_init(&barrier, NULL, 4);
//建立三个子线程
int res;
if ((res = pthread_create(&A, NULL, printThread1, NULL)) != 0)
{
printf("线程A创建失败\n");
exit(-1);
}
//条件置位让线程1开始执行
flag = 1;
if ((res = pthread_create(&B, NULL, printThread2, NULL)) != 0)
{
printf("线程B创建失败\n");
exit(-1);
}
if ((res = pthread_create(&C, NULL, printThread3, NULL)) != 0)
{
printf("线程C创建失败\n");
exit(-1);
}
//设置线程关卡,每个线程运行到关卡均等待
pthread_barrier_wait(&barrier);
//释放内存之前销毁互斥锁和条件变量
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&condition);
pthread_barrier_destroy(&barrier);
return 0;
}
void *printThread1(void *arg)
{
int i = 0;
while (i < 10)
{
pthread_mutex_lock(&lock);
if (flag != 1)
pthread_cond_wait(&condition, &lock);
printf("A");
flag = 2;
i++;
pthread_cond_signal(&condition);
pthread_mutex_unlock(&lock);
}
pthread_barrier_wait(&barrier);
return NULL;
}
void *printThread2(void *arg)
{
int j = 0;
while (j < 10)
{
pthread_mutex_lock(&lock);
if (flag != 2)
pthread_cond_wait(&condition, &lock);
printf("B");
flag = 3;
j++;
pthread_cond_signal(&condition);
pthread_mutex_unlock(&lock);
}
pthread_barrier_wait(&barrier);
return NULL;
}
void *printThread3(void *arg)
{
int k = 0;
while (k < 10)
{
pthread_mutex_lock(&lock);
if (flag != 3)
pthread_cond_wait(&condition, &lock);
printf("C");
flag = 1;
k++;
pthread_cond_signal(&condition);
pthread_mutex_unlock(&lock);
}
pthread_barrier_wait(&barrier);
return NULL;
}
pthread_cond_wait()函数说明:
[1] 抢占到互斥锁并运行到条件if (flag != 1)之后,线程阻塞在条件变量
[2] pthread_cond_wait()内部会解锁,然后等待条件变量被其他线程激活
[3] 被激活后会再自动加锁(在其他线程释放互斥锁之后)
[4] 并重新判断条件变量阻塞条件是否成立,成立重新挂起,不成立则继续往下执行
Acknowledgements:
http://blog.csdn.net/ithomer/article/details/6031723
http://blog.csdn.net/freedom8531/article/details/45419149
2017.08.31