一、互斥锁
1. 函数原型:
pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
pthread_mutex_destroy(pthread_mutex_t *mutex);
分析:
- pthread_mutex_t 类型,其本质是一个结构体,为简化理解,应用时可忽略其实现细节,简单当成整数看待。
- pthread_mutex_t mutex:变量mutex只有两种取值0、1;
函数一参数1:传出参数,调用时应传&mutex
- restrict关键字:只用于限制指针,告诉编译器,所有修改该指针指向内存中内容的操作,只能通过本指针完成。不能通过除本指针以外的其他变量或指针修改。
函数一参数2:互斥属性。是一个传入参数,通常传NULL,选用默认属性(线程间共享).
- 静态初始化:如果互斥锁mutex是静态分配的(定义在全局,或加了static关键字修饰),可以直接使用宏进行初始化。pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
- 动态初始化:局部变量应采用动态初始化, pthread_mutex_init(&mutex, NULL);
pthread_mutex_lock(pthread_mutex_t *mutex);
pthread_mutex_unlock(pthread_mutex_t *mutex);
分析:
- 函数1:没有被上锁,当前线程会将这把锁锁上;被锁上了,当前线程阻塞,锁被打开之后,线程解除阻塞(加锁。可理解为将mutex--(或-1))。
- 函数2:同时将阻塞在该锁上的所有线程全部唤醒解锁(可理解为将mtex++(或+1)).
2. 测试代码:
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>void *tfn(void *arg)
{srand(time(NULL));while(1) {printf("hello ");sleep(rand() % 3); //模拟长时间操作共享资源,导致cpu易主,产生与时间有关的错误printf("word\n");sleep(rand() % 3);}return NULL;
}int main()
{pthread_t tid;srand(time(NULL));pthread_create(&tid, NULL, tfn, NULL);while(1) {printf("HELLO ");sleep(rand() % 3);printf("WORLD\n");sleep(rand() % 3);}return 0;
}
输出结果:
3. 测试代码:
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>void *tfn(void *arg)
{srand(time(NULL));while(1) {printf("hello ");sleep(rand() % 3); //模拟长时间操作共享资源,导致cpu易主,产生与时间有关的错误printf("word\n");sleep(rand() % 3);}return NULL;
}int main()
{pthread_t tid;srand(time(NULL));pthread_create(&tid, NULL, tfn, NULL);while(1) {printf("HELLO ");sleep(rand() % 3);printf("WORLD\n");sleep(rand() % 3);}return 0;
}
输出结果
二、 条件变量
条件变量本身不是锁,但它也可以造成线程阻塞,通常与互斥锁配合使用,给多线程提供一个会合的场所。
1. 函数原型:
pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t * attr); //初始化一个条件变量
pthread_cond_destroy(pthread_cond_t *cond); // 销毁一个条件变量 pthread_cond_signal(pthread_cond_t *cond); // 唤醒至少一个阻塞在条件变量上的线程
pthread_cond_broadcast(pthread_cond_t *cond); // 唤醒全部阻塞在条件变量上的线程
2. 函数原型:
pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
分析:
函数作用:阻塞等待一个条件变量
- 阻塞等待条件变量cond(参数1)满足
- 释放已掌握的互斥锁(解锁互斥量),相当于pthread_mutex_unlock(&mutex); {1、2两步为一个原子操作}
- 当被唤醒,pthread_cond_wait函数返回,解除阻塞并重新获取互斥锁pthread_mutex_lock(&mutex);
3. 函数原型:
函数作用:限时等待一个条件变量
pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
参数3:
struct timespec
{time_t tv_sec; seconds 秒 long tv_nsec; nanoseconds 纳秒
}
形参:abstime:绝对时间
如:time(NULL)返回的就是绝对时间。而alarm(1)是相对时间,相当当前时间定时1秒钟。
struct timespec t = {1, 0};
pthread_cond_time(&cond, &mutex, &t);只能定时到1970年1月1日00:00:01秒(早已经过去)
正确用法:
time_t cur = time(NULL); 获取当前时间
struct timespec t; 定义timespec结构体变量
t.tv_sec = cur + 1; 定时一秒
pthread_cond_timewait(&cond, mutex, &t) 传参
4. 函数原型:
pthread_cond_signal(pthread_cond_t *cond); // 唤醒至少一个阻塞在条件变量上的线程
pthread_cond_broadcast(pthread_cond_t *cond); // 唤醒全部阻塞在条件变量上的线程
三、生产者消费者条件变量模型
线程同步典型的案例即为生产者消费者模型,而借助条件变量来实现这一案例,是比较常见的方法,假定有两个线程,一个模拟生产者行为,一个模拟消费者行为,两个线程同时操作一个共享资源(一般称之为汇聚),生产者向其中添加产品,消费者从中消费掉产品。
1. 测试代码:
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>struct msg
{struct msg *next;int num;
};struct msg *head;
struct msg *mp;pthread_cond_t has_product = PTHREAD_COND_INITIALIZER; //静态初始化:一个条件变量和一个互斥量
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;void *consumer(void *p)
{for (; ;){pthread_mutex_lock(&lock);while (head == NULL) //头指针为空,说明没有节点 可以为if吗 pthread_cond_wait(&has_product, &lock);mp = head;head = mp->next; //模拟消费掉一个产品pthread_mutex_unlock(&lock);printf("-consume-----%d\n", mp->num);free(mp);sleep(rand() % 5);}
}void *producer(void *p)
{for (; ;) {mp = malloc(sizeof(struct msg));mp->num = rand() % 1000 + 1; //模拟生产一个产品printf("-Produce----%d\n", mp->num);pthread_mutex_lock(&lock);mp->next = head;head = mp;pthread_mutex_unlock(&lock);pthread_cond_signal(&has_product); //将等待在该条件变量上的一个线程唤醒sleep(rand() % 5);}
}int main()
{pthread_t pid, cid;srand(time(NULL));pthread_create(&pid, NULL, producer, NULL);pthread_create(&cid, NULL, consumer, NULL);pthread_join(pid, NULL);pthread_join(cid, NULL);return 0;
}
输出结果:
【注意】:为什么用while循环而不用if呢?
一个生产者可能对应着多个消费者,生产者向队列中插入一条数据之后发出signal,然后各个消费者线pthread_cond_wait获取mutex后返回,当然,这里只有一个线程获取到了mutex,然后进行处理,其它线程会pending在这里,处理线程处理完毕之后释放mutex,刚才等待的线程中有一个获取mutex,如果这里用if,就会在当前队列为空的状态下继续往下处理,这显然是不合理的。
五、参考资料
1 深入解析条件变量(condition variables)