1 条件变量
1.1 条件变量
条件变量本身不是锁!但它也可以造成线程阻塞。
通常与互斥锁配合使用。给多线程提供一个会合的场所。
1.2 条件变量的主要相关函数
- pthread_cond_t 类型 用户定义条件变量
- pthread_cond_t cond;
1 初始化条件变量
/*
* function: 初始化一个条件变量
*
* function arguments:
* argv1: 条件变量
* argv2: 条件变量属性,通常为默认值,传NULL
*
* return value:
* success: 返回0
* faild: 返回错误号
*/
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
// 条件变量也可以使用静态初始化的方法:
// pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
2 销毁条件变量
/*
* function: 销毁条件变量
*
* function arguments:
* argv1: 条件变量
*
* return value:
* success: 返回0
* faild: 返回错误号
*/
int pthread_cond_destroy(pthread_cond_t *cond);
3 阻塞等待一个条件变量
/*
* function: 阻塞等待一个条件变量
* 1. 阻塞等待条件变量
* 2. 释放已掌握的互斥锁 相当于 pthread_mutex_unlock(); (1.2两步为同时操作,为一个原子操作)
* 3. 当被唤醒时,pthread_cond_wait函数返回时,解除阻塞并且重新申请获取互斥锁pthread_mutex_lock;
*
* function arguments:
* argv1: 条件变量
* argv2: 互斥锁
*
* return value:
* success: 返回0
* faild: 返回错误号
*/
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
4 限时等待一个条件变量
/*
* function: 限时等待一个条件变量
* 1. 等待条件变量
* 2. 释放已掌握的互斥锁 相当于 pthread_mutex_unlock(); (1.2两步为同时操作,为一个原子操作)
* 3. 当被唤醒时,pthread_cond_wait函数返回时,解除阻塞并且重新申请获取互斥锁pthread_mutex_lock;
* 4. 如果定时时间到了,返回错误
*
* function arguments:
* argv1: 条件变量
* argv2: 互斥锁
* argv3: (可以通过man sem_timedwait函数,查看struct timespec结构体)
* struct timespec{
* time_t tv_sed; // 秒
* long tv_nsec; // 纳秒
* }
*
* 参3,abstime为绝对时间
* 如:time(NULL)返回的就是绝对时间。而 alarm(1)是相对时间,相对当前时间定时 1 秒钟。
* struct timespec t = {1, 0};
* pthread_cond_timedwait (&cond, &mutex, &t); 只能定时到 1970 年 1 月 1 日 00:00:01 秒(早已经过去)
* 正确用法:
* time_t cur = time(NULL); // 获取当前时间。
* struct timespec t; // 定义 timespec 结构体变量 t
* t.tv_sec = cur+1; // 定时 1 秒
* pthread_cond_timedwait (&cond, &mutex, &t);
*
* return value:
* success: 返回0
* faild: 返回错误号
*/
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex,
const struct timespec *restrict abstime);
5 唤醒至少一个阻塞在条件变量上的线程
/*
* function: 唤醒至少一个阻塞在条件变量上的线程
*
* function arguments:
* argv1: 条件变量
*
* return value:
* success: 返回0
* faild: 返回错误号
*/
int pthread_cond_signal(pthread_cond_t *cond);
6 唤醒全部阻塞在条件变量上的线程
/*
* function: 唤醒全部阻塞在条件变量上的线程
*
* function arguments:
* argv1: 条件变量
*
* return value:
* success: 返回0
* faild: 返回错误号
*/
int pthread_cond_broadcast(pthread_cond_t *cond);
1.3 生产者消费者模型
// 实际应用时记得判断pthread系列函数返回值
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 定义并初始化互斥锁
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; // 定义并初始化条件变量
struct msg
{
int num;
struct msg* next;
};
struct msg* head; // 全局区的产品
void *producerThread(void *argv) // 生产者线程
{
while (1)
{
// 1.生产产品
struct msg *tmp = new msg{};
tmp->num = rand()%1000 + 1;
printf("product-------%d\n", tmp->num);
pthread_mutex_lock(&mutex); // 2.加锁
// 3.将生产的产品放入全局区
tmp->next = head;
head = tmp;
pthread_mutex_unlock(&mutex); // 4.解锁
pthread_cond_signal(&cond); // 5.唤醒等待此条件变量的线程
sleep(rand()%3);
}
pthread_exit(NULL);
}
void *consumerThread(void *argv) // 消费者线程
{
while (1)
{
struct msg *tmp;
pthread_mutex_lock(&mutex); // 1.加锁
// 2.等待产品被生产出来(当全局区产品为空时,等待有产品被生产出)
while (head == NULL) // 对于一个生产者多个消费者,此处不能使用if(因为有可能当被唤醒时,产品已经被别的消费者使用,需要重新判断是否有还有产品)
{
pthread_cond_wait(&cond, &mutex);
}
// 3.模拟消费一个产品
tmp = head;
head = tmp->next;
printf("consume------------%d\n", tmp->num);
pthread_mutex_unlock(&mutex); // 4.解锁
delete tmp;
sleep(rand()%3);
}
pthread_exit(NULL);
}
int main()
{
srand(time(NULL));
pthread_t pID, cID;
int ret = pthread_create(&pID, NULL, producerThread, NULL); // 创建生产者线程
if (0 != ret)
{
printf("pthread_create producerThread error:%s\n", strerror(ret));
return -1;
}
ret = pthread_create(&pID, NULL, consumerThread, NULL); // 创建消费者线程
if (0 != ret)
{
printf("pthread_create consumerThread error:%s\n", strerror(ret));
return -1;
}
// 回收生产者、消费者线程
pthread_join(pID, NULL);
pthread_join(cID, NULL);
// 销毁互斥锁、条件变量
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
pthread_exit(NULL);
}
2 信号量
2.1 信号量
互斥锁为初始值为1。
信号量相当于初始值为N的互斥量。N值,表示可以同时访问共享数据区的线程数。
2.2 信号量的主要相关函数
- sem_t 类型 用户定义条件变量
- 本质是结构体。但应用期间可简单看作为整数,忽略实现细节
- sem_t sem;
- 规定信号量 sem 不能 < 0
- 头文件 <semaphore.h>
1 初始化
/*
* function: 初始化一个信号量
*
* function arguments:
* argv1: 信号量
* argv2: 0-用于线程间,非0-用于进程间
* argv3: 指定信号量的初值(前文信号量概念中的N)
*
* return value:
* success: 返回0
* faild: 返回-1,并设置errno
*/
int sem_init(sem_t *sem, int pshared, unsigned int value);
2 销毁信号量
/*
* function: 销毁一个信号量
*
* function arguments:
* argv1: 信号量
*
* return value:
* success: 返回0
* faild: 返回-1,并设置errno
*/
int sem_destroy(sem_t *sem);
3 加锁
/*
* function: 给信号量加锁 --操作
*
* function arguments:
* argv1: 信号量
*
* return value:
* success: 返回0
* faild: 返回-1,并设置errno
*/
int sem_wait(sem_t *sem);
4 解锁
/*
* function: 给信号量解锁 ++操作
*
* function arguments:
* argv1: 信号量
*
* return value:
* success: 返回0
* faild: 返回-1,并设置errno
*/
int sem_post(sem_t *sem);
5 尝试加锁
/*
* function: 尝试对信号量加锁 --操作 (与 sem_wait 的区别类比 lock 和 trylock)
*
* function arguments:
* argv1: 信号量
*
* return value:
* success: 返回0
* faild: 返回-1,并设置errno
*/
int sem_trywait(sem_t *sem);
6 限时加锁
/*
* function: 限时尝试对信号量加锁 --操作
*
* function arguments:
* argv1: 信号量
* argv2: (可以通过man sem_timedwait函数,查看struct timespec结构体)
* struct timespec{
* time_t tv_sed; // 秒
* long tv_nsec; // 纳秒
* }
*
* 参2,abs_timeout为绝对时间
* 如:time(NULL)返回的就是绝对时间。而 alarm(1)是相对时间,相对当前时间定时 1 秒钟。
* struct timespec t = {1, 0};
* sem_timedwait(&sem, &t); 只能定时到 1970 年 1 月 1 日 00:00:01 秒(早已经过去)
* 正确用法:
* time_t cur = time(NULL); // 获取当前时间。
* struct timespec t; // 定义 timespec 结构体变量 t
* t.tv_sec = cur+1; // 定时 1 秒
* sem_timedwait(&sem, &t);
*
* return value:
* success: 返回0
* faild: 返回-1,并设置errno
*/
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
2.3 加锁和解锁
sem_wait: 1. 信号量大于 0,则信号量-- (类比 pthread_mutex_lock)
| 2. 信号量等于 0,造成线程阻塞
对应
|
sem_post: 将信号量++,同时唤醒阻塞在信号量上的线程 (类比 pthread_mutex_unlock)
但,由于 sem_t 的实现对用户隐藏,所以所谓的++、 --操作只能通过函数来实现,而不能直接++、 --符号。
信号量的初值,决定了占用信号量的线程的个数。
2.4 生产者消费者模型
// 信号量实现生产者消费者模型
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <semaphore.h>
#define NUM 5
int queue[NUM] = {0}; // 全局数组实现环形队列
// 定义全局的信号量(空格子信号量、产品信号量)
sem_t g_blank, g_product;
void *producerThread(void *argv) // 生产者线程
{
int i = 0;
while (1)
{
sem_wait(&g_blank); // 生产者将空格子数--,为0则阻塞等待
queue[i] = rand()%1000 + 1; // 生产一个产品
printf("product-------%d\n", queue[i]);
sem_post(&g_product); // 将产品数++
i = (i+1) % NUM; // 借助下标实现环形队列
sleep(rand()%3);
}
pthread_exit(NULL);
}
void *consumerThread(void *argv) // 消费者线程
{
int i = 0;
while (1)
{
sem_wait(&g_product); // 消费者将产品数--,为0则阻塞等待
printf("consume------------%d\n", queue[i]); // 消费一个产品
queue[i] = 0;
sem_post(&g_blank); // 消费掉以后,将空格子数++
i = (i+1) % NUM;
sleep(rand()%3);
}
pthread_exit(NULL);
}
int main()
{
srand(time(NULL));
// 初始化信号量
sem_init(&g_blank, 0, NUM); // 初始化空格子信号量为5
sem_init(&g_product, 0, 0); // 最开始产品数为0
pthread_t pID, cID;
int ret = pthread_create(&pID, NULL, producerThread, NULL); // 创建生产者线程
if (0 != ret)
{
printf("pthread_create producerThread error:%s\n", strerror(ret));
return -1;
}
ret = pthread_create(&pID, NULL, consumerThread, NULL); // 创建消费者线程
if (0 != ret)
{
printf("pthread_create consumerThread error:%s\n", strerror(ret));
return -1;
}
// 回收生产者、消费者线程
pthread_join(pID, NULL);
pthread_join(cID, NULL);
// 销毁信号量
sem_destroy(&g_blank);
sem_destroy(&g_product);
pthread_exit(NULL);
}