信号量

Friday, November 8, 2019

信号量

信号量是有一个整数值的对象,通过 sem_wait 和 sem_post() 两个函数来操作。

sem_t s;
sem_init(&s, 0, 1);

第一个参数是申明了一个信号量 s,第二个参数一般都设为0,代表同一进程的多个线程都能访问,第三个参数则是将初始值设为1。

当值大于等于1时,sem_wait 会立刻返回,否则则会调用线程挂起,等待 sem_post 的操作。

sem_post 没有其他操作,直接增加信号量的值,如果有挂起的线程,唤醒其中一个。

信号量为负数时,这个值就是等待的线程个数。

二值信号量(锁)

简单来讲就是把信号量作为锁来使用。

sem_t m;
sem_init(&m, 0, x);

sem_wait(&m);
//do something
sem_post(&m);

当锁来使用的时候,我们可以把 x 定义为1。

当 x 变为小于0时,线程会挂起等待,直到另一个线程调用了 post,把 x 值增加,这个线程才会获得锁,并执行相关代码。

信号量作为条件变量

信号量也可以作为条件变量来使用,需要等待的线程执行 wait 方法,另一个线程执行完之后,调用 post 方法,从而达到条件变量的效果。在这里,x 的初始值就要设为0了。这样调用 wait 的时候,才能马上进行挂起休眠。

void *child(void *arg) {
    printf("child\n");
    sem_post(&s);
    return NULL;
}

int main(...) {
    sem_init(&s, 0, 0);
    printf("parent: begin.\n");
    pthread_t c;
    Pthread_create(c, NULL, child, NULL);
    sem_wait(&s);
    printf("parent: end\n");
    return 0;
}

信号量实现生产者/消费者模型

这里我们需要考虑到互斥和死锁的问题,所以最终的代码结构类似这样:

sem_t empty;
sem_t full;
sem_t mutex;

void *producer(void *arg) {
    int i;
    for (i = 0; i < loops; i++) {
        sem_wait(&empty);
        sem_wait(&mutex);
        put(i);
        sem_post(&mutex);
        sem_post(&full);
    }
}

void *consumer(void *arg) {
    int i;
    for (i = 0; i < loops; i++) {
        sem_wait(&full);
        sem_wait(&mutex);
        int tmp = get();
        sem_post(&mutex);
        sem_post(&empty);
        printf("%d\n",tmp);
    }
}

读者-写者锁

该锁的主要思想与前面的生产/消费不同的是,我们可以并发的去读,提高性能表现。

typedef struct _rwlock_t {
    sem_t lock;
    sem_t writelock;
    int readers;
} rowlock_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);
}

这里就只写了读的获取和释放锁,写的获取和释放锁只是简单的 wait 和 post,就不再阐述。 从代码里可以看到,第一个读者获取锁时,也会获取写锁。

所以可以看到,不管加了读锁还是写锁,都不能继续加写锁。 如果没有加写锁,则可以多个线程同时加读锁,如果有加写锁,则不可以加读锁。

OS

常见并发场景

条件变量