条件变量
什么是条件变量?
- 条件变量是进行线程阻塞的一种机制,经常和
互斥锁
结合起来处理生产者消费者模型 - 条件变量给多线程提供了一个会合的场所。条件变量与互斥锁一起使用时,允许线程以
无竞争的方式
等待特定的条件发生 - 条件变量只有满足特定条件(如,任务队列已满或已空)时才会阻塞线程;如果条件不满足,多个线程可以同时进入临界区,同时读写共享资源,因此还是会造成共享资源的混乱;因此条件变量通常要和互斥锁一起使用,利用互斥锁保证线程同步。
条件变量相关的函数API
#include <pthread.h>
pthread_cond_t cond; // 被条件变量阻塞的线程的线程信息会被记录到这个变量中,以便在解除阻塞的时候使用
// 初始化
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
// 销毁释放资源
int pthread_cond_destroy(pthread_cond_t *cond);
// 线程阻塞函数, 哪个线程调用这个函数, 哪个线程就会被阻塞
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
// 表示的时间是从1971.1.1到某个时间点的时间, 总长度使用秒/纳秒表示
struct timespec {
time_t tv_sec; /* Seconds */
long tv_nsec; /* Nanoseconds [0 .. 999999999] */
};
// 将线程阻塞一定的时间长度, 时间到达之后, 线程就解除阻塞了
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
// 唤醒阻塞在条件变量上的线程, 至少有一个被解除阻塞
int pthread_cond_signal(pthread_cond_t *cond);
// 唤醒阻塞在条件变量上的线程, 被阻塞的线程全部解除阻塞
int pthread_cond_broadcast(pthread_cond_t *cond);
链接: https://subingwen.cn/linux/thread-sync/#5-1-%E6%9D%A1%E4%BB%B6%E5%8F%98%E9%87%8F%E5%87%BD%E6%95%B0
生产者与消费者模型的源代码
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#define QUEUEMAX 10
// 互斥锁与条件变量
pthread_mutex_t mutex;
pthread_cond_t cons_cond;
pthread_cond_t prod_cond;
// 节点
typedef struct node
{
int num;
struct node* next;
}Node;
// 指向头结点的指针
Node* head = NULL;
int count = 0;
/* 节点插入方式如下所示
* head-->null
* newNode1
* head-->newNode1-->null
* newNode2
* head-->newNode2-->newNode1-->null
* .....
*/
void* producer(void* arg) {
// 创建节点
while (1) {
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->num = rand() % 100;
// 上锁
pthread_mutex_lock(&mutex);
// 假如队列已经满,阻塞生产者线程
// 用while不能用if
while (count >= 10) {
pthread_cond_wait(&prod_cond, &mutex); // 记得唤醒哦
}
newNode->next = head;
head = newNode;
count++;
printf("+++Producer id: %ld, number: %d, count: %d\n", pthread_self(), newNode->num, count);
// 解锁
pthread_mutex_unlock(&mutex);
// 唤醒至少一个阻塞的消费者线程
pthread_cond_signal(&cons_cond);
sleep((unsigned int)rand() % 2);
}
return NULL;
}
void* consumer(void* arg) {
while (1) {
// 上锁
pthread_mutex_lock(&mutex);
// 假如队列已空,应该让消费者线程阻塞
while (head == NULL || count == 0) {
pthread_cond_wait(&cons_cond, &mutex); // 要记得唤醒哦!
}
Node* curNode = head;
count--;
printf("---Consumer id: %ld, number: %d, count: %d\n", pthread_self(), curNode->num, count);
head = head->next;
free(curNode);
// 解锁
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&prod_cond);
sleep((unsigned int)rand() % 3);
}
return NULL;
}
int main()
{
// 初始化锁和条件变量
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&cons_cond, NULL);
// t1是生产者线程数组 t2是消费者线程数组
pthread_t t1[5], t2[5];
// 创建线程
for (int i = 0; i < 5; i++) {
pthread_create(&t1[i], NULL, producer, NULL);
}
for (int i = 0; i < 5; i++) {
pthread_create(&t2[i], NULL, consumer, NULL);
}
// 回收线程
for (int i = 0; i < 5; i++) {
pthread_join(t1[i], NULL);
}
for (int i = 0; i < 5; i++) {
pthread_join(t2[i], NULL);
}
// 销毁互斥锁和条件变量
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cons_cond);
return 0;
}
参考文献:
①《程序员的自我修养》
② https://subingwen.cn/linux/thread-sync/