目录
#include <semaphore.h>
sem_t s;
sem_init(&s, 0, 1);
// param2:0:信号量在同进程的多线程中共享
// param3:信号量的值
int sem_wait(sem_t* s) {
// s--;
s>=0 -> 立即返回
s<0 线程挂起,直到之后的post操作
}
int sem_post(sem_t* s) {
// s++;
直接增加信号量的值,如果有等待的线程,唤醒其中一个。
}
// 当信号量 < 0 时,s的val就是等待线程的个数。
二值信号量(锁)
#include <semaphore.h>
sem_t s;
sem_init(&s, 0, 1);
// param2:0:信号量在同进程的多线程中共享
// param3:信号量的值
int sem_wait(sem_t* s) {
// s--;
s>=0 -> 立即返回
s<0 线程挂起,直到之后的post操作
}
int sem_post(sem_t* s) {
// s++;
直接增加信号量的值,如果有等待的线程,唤醒其中一个。
}
// 当信号量 < 0 时,s的val就是等待线程的个数。
信号量的第一种用法:作为锁来使用
直接把临界区用一对sem_wait() / sem_post()包括起来。
0值信号量(条件变量)
信号量的第一种用法:作为条件变量来使用
父线程wait等待子线程
子线程done后调用post进行signal
sem_t* s; void* child(void* arg) { printf("child\n"); sem_post(&s); // signal : child done return NULL; } int main() { sem_init(&s, 0, 0); // 初始化值为0 printf("parent:begin\n"); pthread_t p; Pthread_create(p, NULL, child, NULL); sem_wait(&s); // wait : wait for child done printf("parent:end\n"); return 0; }综合使用条件变量和
nt buffer[MAXN]; int fill_ptr = 0; int use_ptr = 0; int count = 0; void put(int value) { buffer[fill_ptr] = value; fill_ptr = (fill_ptr + 1) % MAXN; count++; } int get() { int tmp = buffer[use_ptr]; use_ptr = (use_ptr + 1) % MAXN; count--; return tmp; } sem_t empty; sem_t full; sem_t mutex; void* producer(void* arg) { for (int i = 0; i < loops; i++) { sem_wait(&empty); // 等到空的时候进行生产 sem_wait(&mutex); // 为了保证生产者互斥插入,需要加锁 put(i); sem_post(&mutex); // 解锁 sem_post(&full); // 生产好了,提醒消费者进行消费 } } void* consumer(void* arg) { for (int i = 0; i < loops; i++) { sem_wait(&full); // 等到生产好了进行消费 sem_wait(&mutex); // 为了保证消费者互斥插入,需要加锁 put(i); sem_post(&mutex); // 解锁 sem_post(&empty); // 消费好了,提醒生产者进行生产 } }举例:
在这里出现了死锁,因为sem_wait在让出CPU时并没有释放锁,而在https://www.wolai.com/dNjJbFaR3fYqd6CXP27HAe中,我们使用的Pthread_cond_wait(&empty, &mutex);会对锁进行释放,从而避免死锁。这两个API的区别注意一下。
读写锁
访问同一个数据结构可能需要不同类型的锁。读写锁就是一种。
下面是不公平的读写锁:写锁只能在最后读者释放之后才能被写者获取。
rwlock_acquire_writelock() 获得写锁 rwlock_release_writelock() 释放写锁 typedef struct _rwlock_t { sem_t lock; // 二值信号量作为basic lock sem_t writelock; // 用于一个写者 or 多个读者 int readers; // 在临界区读取的读者数 } rwlock_t; void rwlock_init(rwlock_t* rw) { rw->readers = 0; sem_init(&rw->lock, 0, 1); sem_init(&rw->writelock, 0, 1); } void rwlock_acquire_readlock(rwlock_t* rw) { sem_wait(&rw->lock); rw->readers++; if (rw->readers == 1) { sem_wait(&rw->writelock); // 第一个读者需要持有写锁 } sem_post(&rw->lock); } void rwlock_release_readlock(rwlock_t* rw) { sem_wait(&rw->lock); rw->readers--; if (rw->readers == 0) { sem_post(&rw->writelock); // 最后一个读者释放写锁 } sem_post(&rw->lock); } void rwlock_acquire_writelock(rwlock_t* rw) { sem_wait(&rw->writelock); } void rwlock_release_writelock(rwlock_t* rw) { sem_post(&rw->writelock); }一旦一个读者获得了读锁,其他的读者也可以获取这个读锁。
但是,想要获取写锁的线程,就必须等到所有的读者都结束。
最后一个退出的写者在“writelock”信号量上调用sem_post(),从而让等待的写者能够获取该锁。
读者很容易饿死写者,我们应该思考有写者等待时,如何能够避免更多的读者进入并持有锁。
这里TODO一下,以后看到有好的代码写过来。
假定有5位“哲学家”围着一个圆桌。每两位哲学家之间有一把餐叉(一共5把)。哲学家有时要思考一会,不需要餐叉;有时又要就餐。而一位哲学家只有同时拿到了左手边和右手边的两把餐叉,才能吃到东西。
每个哲学家(线程)的自我循环:
while (1) { think(); getforks(); eat(); putforks(); }辅助函数:
int left(int p) { // 使用左边的叉子 return p; } int right(int p) { // 使用右边的叉子 return (p + 1) % 5; }sem_t forks[5]; void getforks() { sem_wait(forks[left(p)]); sem_wait(forks[right(p)]); }依次获取每把餐叉的锁——先是左手边的,然后是右手边的。结束就餐时,释放掉锁。假设每个哲学家都拿到了左手边的餐叉,他们每个都会阻塞住,并且一直等待另一个餐叉。
解决上述问题最简单的方法,就是修改某个或者某些哲学家的取餐叉顺序。
假定哲学家4(编写最大的一个)取餐叉的顺序不同。
因为最后一个哲学家会尝试先拿右手边的餐叉,然后拿左手边,所以不会出现每个哲学家都拿着一个餐叉,卡住等待另一个的情况,等待循环被打破了。
使用锁+条件变量实现信号量
typedef struct _Zem_t { int value; pthread_cond_t cond; pthread_mutex_t lock; } Zem_t; // only one thread can call it void Zem_init(Zem_t* s, int value) { s->value = value; } void Zem_wait(Zem_t* s) { Pthread_mutex_lock(&s->lock); while (s->value <= 0) { // 当小于0 阻塞 并在睡眠前将锁释放,这样下面的unlock就不会出错 Pthread_cond_wait(&s->cond, &s->lock); } s->value--; Pthread_mutex_unlock(&s->lock); } void Zem_post(Zem_t* s) { Pthread_mutex_lock(&s->lock); s->value++; Pthread_cond_signal(&s->cond); Pthread_mutex_unlock(&s->lock); }扩展
关于信号量的之前也有部分笔记:
No comments yet.