互斥锁
锁”是一个很普遍的需求,“为临界区上锁”实际描述的需求是:非临界区的任务可以同时执行,临界区中的任务只允许一个线程执行、不允许多个线程同时执行。
1. 定义互斥锁变量
pthread_mutex_t mutex;
2. 初始化互斥锁变量
//静态初始化--不用销毁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//动态初始化
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
功能:初始化一个互斥锁变量
参数:
mutex: 互斥锁变量的地址
attr : 互斥锁变量的属性 NULL
返回值:成功0 失败Errno
3. 上锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
功能:锁定互斥锁变量--上锁
参数:
mutex: 互斥锁变量的地址
int pthread_mutex_trylock(pthread_mutex_t *mutex);
功能:尝试锁定互斥锁变量--尝试上锁
参数:
mutex: 互斥锁变量的地址
【当一个线程执行pthread_mutex_lock动作时,如果此时互斥锁变量被其他线程锁定,那么调用线程会阻塞直到该锁可用
当一个线程执行pthread_mutex_trylock动作时,如果此时互斥锁变量被其他线程锁定,那么调用线程会直接返回】
4. 解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
功能:释放互斥锁变量
参数:
mutex: 互斥锁变量的地址
5. 销毁锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
只能对一个已经init初始化的互斥锁变量进行销毁
实例:
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
//定义互斥锁变量
pthread_mutex_t mutex;
int num = 150; //临界资源
void *thread(void *arg){
int id = *(int *)arg;
for(int i=0;i<5;i++){
//上锁
pthread_mutex_lock(&mutex);
num-=10;
printf("%d : num = %d\n", id, num);
//解锁
pthread_mutex_unlock(&mutex);
}
pthread_exit(NULL);
}
int main(){
pthread_t pt[3];
int array[3] = {0};
//初始化互斥锁变量
pthread_mutex_init(&mutex, NULL);
for(int i=0;i<3;i++){
array[i] = i; //不能对线程传参为可变的变量
if(0 != pthread_create(pt+i, NULL, thread, (void *)&array[i])){
perror("pthread_create");return -1;
}
}
for(int i=0;i<3;i++){
pthread_join(pt[i], NULL);
}
//销毁互斥锁变量
pthread_mutex_destroy(&mutex);
return 0;
}
条件变量
与“互斥锁”的概念不同,条件变量是用来等待而不是用来上锁的,其本身不是锁。
锁通常用于保护临界区,避免多个线程同时操作临界资源,而条件变量通常用于阻塞一个线程的运行,然后在合适的时机将其唤醒,例如:线程B的工作是处理数据,线程A的工作是获取数据,那么线程A在没有获取到有效数据之前线程B是不应当进行工作的,这时可以使用条件变量暂时将线程B阻塞,等待线程A获取到数据之后再将线程B唤醒运行。
条件变量通常和互斥锁变量搭配使用,通常进行两个动作:
(1)条件不满足,阻塞线程;
(2)条件满足,解除线程的阻塞状态。
互斥锁的主要目标是为了安排【互斥】任务
条件变量的主要目标是为了安排【同步】任务
【同步】:是指散布在不同任务之间的若干程序片断,它们的运行必须严格按照规定的某种先后次序来运行,这种先后次序依赖于要完成的特定的任务。最基本的场景就是:两个或两个以上的进程或线程在运行过程中协同步调,按预定的先后次序运行。比如 A 任务的运行依赖于 B 任务产生的数据。
1. 定义条件变量
pthread_cond_t cond;
2. 初始化
//静态
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
//动态
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
功能:初始化
参数:
cond: 条件变量的地址
attr: 属性 NULL
3. 判断条件是否满足(等待条件信号到达)-获取条件变量的资源--获取之前要先使用互斥锁,并且在函数内部会释放该互斥锁
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex,
const struct timespec *restrict abstime);
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
功能:获取条件变量的资源(原子操作:不能被另一个线程或者进程打断)
判断条件是否满足,如果满足,就解除阻塞上锁获取资源后移出队列
如果不满足,就一直在队列休眠
(1)上锁 为了放入等待队列
(2)调用pthread_cond_wait函数
a. 进入队列
b. 解锁,阻塞 [ 解锁解的是放入队列时争夺的资源锁 ]
c. 等待条件满足
d. 争夺锁变量 [ 争夺获取资源的锁变量,一旦获取资源就可以移出队列 ]
e. 执行动作,移出队列
4. 释放资源-唤醒队列中阻塞的线程
// 发送条件信号 - 唤醒被该条件阻塞的一个线程
int pthread_cond_signal(pthread_cond_t *cond);
// 发送条件信号 - 唤醒所有被该条件阻塞的线程
int pthread_cond_broadcast(pthread_cond_t *cond);
5. 销毁条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
生产者消费者模型:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
//一个生产者和多个消费者模型
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int computer = 0; //产品-临界资源
void *product(void *arg){
int id = *(int *)arg;
while(1){
//上锁
pthread_mutex_lock(&mutex);
//生产产品 对临界资源操作
computer++;
printf("生产者 %d 生产1电脑\n", id);
//唤醒一个线程
pthread_cond_signal(&cond);
//pthread_cond_broadcast(&cond);
//解锁
pthread_mutex_unlock(&mutex);
sleep(1); //生产耗时
}
}
void *consumer(void *arg){
int id = *(int *)arg;
while(1){
//上锁
pthread_mutex_lock(&mutex);
//等待条件满足
pthread_cond_wait(&cond, &mutex);
//购买产品 对临界资源操作
computer--;
printf("消费者 %d 购买一台电脑 剩余电脑[%d]\n", id, computer);
//解锁
pthread_mutex_unlock(&mutex);
sleep(1); //消费耗时
}
}
int main(){
#if 0
pthread_t ptid; //生产
if(0 != pthread_create(&ptid, NULL, product, NULL)){
perror("ptid");return -1;
}
#endif
pthread_t ptid[2]; //生产
pthread_t ctid[5]; //消费
int arr[5] = {0};
//生产
for(int i=0;i<2;i++){
arr[i] = i+1;
if(0 != pthread_create(&ptid[i], NULL, product, (void *)&arr[i])){
perror("ptid");return -1;
}
}
//消费
for(int i=0;i<5;i++){
arr[i] = i;
if(0 != pthread_create(&ctid[i], NULL, consumer, (void *)&arr[i])){
perror("ctid");return -1;
}
}
//pthread_join(ptid, NULL);
for(int i=0;i<2;i++){
pthread_join(ptid[i], NULL);
}
for(int i=0;i<5;i++){
pthread_join(ctid[i], NULL);
}
return 0;
}