操作系统笔记(13)-信号量
操作系统笔记(13)-信号量
简介
- 信号量是另一种提供线程同步的方式,可以使用信号量作为锁和条件变量
POSIX 信号量 API
#include <semaphore.h>
// 初始化信号量
// 1. sem_t 变量
// 2. pshared 为0表示这个信号量是当前进程的局部信号量, 否则该信号量可以在多个进程之间共享
// 3. value 指定信号量初始值
int sem_init(sem_t* sem, int pshared, unsigned int value);
// 销毁信号量
int sem_destroy(sem_t* sem);
// 以原子操作的方式将信号量的值减一。 减完后信号量的值若小于0,就等待至信号量大于等于0
int sem_wait(sem_t* sem);
// 始终立即返回, 当信号量为0时返回-1并设置errno
int sem_trywait(sem_t* sem);
// 原子地将信号量加一
int sem_post(sem_t* sem);
信号量充当互斥锁
- 将信号量的初始值设置为
1
即可
#include <stdio.h>
#include <semaphore.h>
#include <pthread.h>
int counter = 0;
sem_t mutex;
void* add(void *arg) {
int loops = *(int*)arg;
for (int i = 0; i < loops; i++) {
sem_wait(&mutex);
++counter;
sem_post(&mutex);
}
}
int main(int argc, char* argv[]) {
int cnt;
printf("Please input count : ");
scanf("%d", &cnt);
sem_init(&mutex, 0, 1);
pthread_t t[2];
pthread_create(&t[0], NULL, add, (void*)&cnt);
pthread_create(&t[1], NULL, add, (void*)&cnt);
pthread_join(t[0], NULL);
pthread_join(t[1], NULL);
printf("counter : %d\n", counter);
sem_destroy(&mutex);
return 0;
}
信号量充当条件变量
- 将信号量的初始值设置为
0
即可, 满足条件后post
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
sem_t cond;
void* child(void *arg) {
printf("child\n");
// do something...
sleep(1);
sem_post(&cond); // 相当于唤醒
}
int main(int argc, char* argv[]) {
printf("parent : begin\n");
sem_init(&cond, 0, 0);
pthread_t p;
pthread_create(&p, NULL, child, NULL);
sem_wait(&cond); // 主线程等待子线程完成任务
printf("parent : end\n");
sem_destroy(&cond);
return 0;
}
利用信号量解决生产者/消费者问题
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#define MAX 20
int buffer[MAX];
int fill_ptr;
int use_ptr;
sem_t full, empty;
sem_t mutex;
void put(int value) {
buffer[fill_ptr] = value;
fill_ptr = (fill_ptr + 1) % MAX;
}
int get() {
int value = buffer[use_ptr];
use_ptr = (use_ptr + 1) % MAX;
return value;
}
void* producer(void *arg) {
int loops = *(int*)arg;
for (int i = 0; i < loops; i++) {
sem_wait(&empty); // 如果有空位
sem_wait(&mutex);
put(i);
printf("put : %d\n", i);
sem_post(&mutex);
sem_post(&full); // 增加一个非空位
}
}
void* consumer(void *arg) {
int loops = *(int*)arg;
for (int i = 0; i < loops; i++) {
sem_wait(&full); // 如果有非空位
sem_wait(&mutex);
int value = get();
printf("get : %d\n", value);
sem_post(&mutex);
sem_post(&empty); // 增加一个空位
}
}
利用信号量实现读写锁
读写锁允许一个线程获得
写锁
, 多个线程获得读锁
,这样可以让多个线程同时进行读
操作读写锁实现如下:
#ifndef _READER_WRITER_LOCK_H_
#define _READER_WRITER_LOCK_H_
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
typedef struct rwlock_t {
sem_t lock;
sem_t writelock;
int readers;
} rwlock_t;
// 初始化读写锁
void rwlock_init(rwlock_t *rw) {
sem_init(&rw->lock, 0, 1);
sem_init(&rw->writelock, 0, 1);
rw->readers = 0;
}
// 获得读锁
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);
}
#endif
测试
#include "reader-writer-lock.h"
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int loops;
int value = 0;
rwlock_t lock;
void *reader(void *arg) {
int i;
for (i = 0; i < loops; i++) {
rwlock_acquire_readlock(&lock);
printf("read %d\n", value);
rwlock_release_readlock(&lock);
}
return NULL;
}
void *writer(void *arg) {
int i;
for (i = 0; i < loops; i++) {
rwlock_acquire_writelock(&lock);
value++;
printf("write %d\n", value);
rwlock_release_writelock(&lock);
}
return NULL;
}
int main(int argc, char *argv[]) {
assert(argc == 4);
int num_readers = atoi(argv[1]);
int num_writers = atoi(argv[2]);
loops = atoi(argv[3]);
pthread_t pr[num_readers], pw[num_writers];
rwlock_init(&lock);
printf("begin\n");
int i;
for (i = 0; i < num_readers; i++)
pthread_create(&pr[i], NULL, reader, NULL);
for (i = 0; i < num_writers; i++)
pthread_create(&pw[i], NULL, writer, NULL);
for (i = 0; i < num_readers; i++)
pthread_join(pr[i], NULL);
for (i = 0; i < num_writers; i++)
pthread_join(pw[i], NULL);
printf("end: value %d\n", value);
return 0;
}
缺点 :一旦一个读者获得了 读锁
,其他的读者也可以获取这个读锁
。但是,想要获取 写锁
的线程,就必须等到所有的读者都结束,因此 读者很容易饿死写者
, 并且和其他一些简单快速的锁相比,读写锁在性能方面没有优势
哲学家就餐问题
这是一个著名的并发问题,问题的基本情况是:假定有
5
位“哲学家”围着一个圆桌,每两位哲学家之间会有一把叉子(一共5
把)。哲学家有时要思考一会儿,不需要叉子;有时又要就餐,而一位哲学家只有同时拿到了左手边和右手边的两把叉子,才能吃到东西下面是每个哲学家的基本循环
while (1) { think(); // 思考 getforks(); // 拿起左右两把叉子 eat(); // 就餐 putforks(); // 放下两把叉子 } * 辅助函数,用于定位哲学家 `p` 左右两边叉子的编号 ```c int left(int p) { return p; } int right(int p) { return (p + 1) % 5; }
有问题的方案
void getforks() {
sem_wait(forks[left(p)]);
sem_wait(forks[right(p)]);
}
void putforks() {
sem_post(forks[left(p)]);
sem_post(forks[right(p)]);
}
- 如果每个哲学家都是先拿到左边的叉子然后再拿右边的叉子,那么就可能出现
死锁
的情况,即所有哲学家都拿起了左边的叉子,而一直等待右边的叉子
破除依赖
- 为了解决这个问题,我们可以
修改某个或者某些哲学家的取餐顺序
,这样总有哲学家能够吃到东西
void getforks() {
if (p == 0) { // 破除依赖
sem_wait(forks[right(p)]);
sem_wait(forks[left(p)]);
}
else {
sem_wait(forks[left(p)]);
sem_wait(forks[right(p)]);
}
}
void putforks() {
if (p == 0) {
sem_post(forks[right(p)]);
sem_post(forks[left(p)]);
}
else {
sem_post(forks[left(p)]);
sem_post(forks[right(p)]);
}
}
测试
#include <stdio.h>
#include <assert.h>
#include <pthread.h>
#include <semaphore.h>
sem_t forks[5];
pthread_t philosophers[5];
int left(int p) { return p; }
int right(int p) { return (p + 1) % 5; }
void eat(int p) {
printf("%d is eating !\n", p);
}
void getforks(int p) {
if (p == 0) { // 破除依赖
sem_wait(&forks[right(p)]);
sem_wait(&forks[left(p)]);
}
else {
sem_wait(&forks[left(p)]);
sem_wait(&forks[right(p)]);
}
}
void putforks(int p) {
if (p == 0) {
sem_post(&forks[right(p)]);
sem_post(&forks[left(p)]);
}
else {
sem_post(&forks[left(p)]);
sem_post(&forks[right(p)]);
}
}
void* wantToEat(void *arg) {
int p = *(int*)arg;
getforks(p);
eat(p);
putforks(p);
return NULL;
}
int main(int argc, char *argv[]) {
for (int i = 0; i < 5; i++) {
sem_init(&forks[i], 0, 1);
}
int id[5];
for (int i = 0; i < 5; i++) {
id[i] = i;
pthread_create(&philosophers[i], NULL, wantToEat, (void*)&id[i]);
}
for (int i = 0; i < 5; i++) {
pthread_join(philosophers[i], NULL);
}
return 0;
}
/* output:
4 is eating !
3 is eating !
2 is eating !
1 is eating !
0 is eating !
*/
如何实现信号量
我们可以利用
锁
和条件变量
实现一个信号量#ifndef _ZEM_H_ #define _ZEM_H_ #include <pthread.h> typedef struct Zem_t { int value; pthread_mutex_t mutex; pthread_cond_t cond; } Zem_t; void Zem_init(Zem_t *s, int value) { s->value = value; pthread_mutex_init(&s->mutex, NULL); pthread_cond_init(&s->cond, NULL); } void Zem_wait(Zem_t *s) { pthread_mutex_lock(&s->mutex); while (s->value <= 0) pthread_cond_wait(&s->cond, &s->mutex); --s->value; pthread_mutex_unlock(&s->mutex); } void Zem_post(Zem_t *s) { pthread_mutex_lock(&s->mutex); ++s->value; pthread_cond_signal(&s->cond); pthread_mutex_unlock(&s->mutex); } void Zem_destroy(Zem_t *s) { pthread_mutex_destroy(&s->mutex); pthread_cond_destroy(&s->cond); } #endif
All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.