📜  操作系统中的信号量解决方案(1)

📅  最后修改于: 2023-12-03 15:39:52.786000             🧑  作者: Mango

操作系统中的信号量解决方案

什么是信号量?

在操作系统中,信号量是一种用于处理多道程序并发执行的进程同步机制。它主要用于控制进程对共享资源的访问,以保证多个进程能够在访问共享资源时不会发生冲突。

信号量的基本操作

在 Linux 中,我们可以使用 semaphore.h 头文件中提供的函数来对信号量进行操作。信号量的基本操作包括三个函数:

sem_init()
int sem_init(sem_t *sem, int pshared, unsigned int value);

这个函数用于初始化一个信号量,其中参数 sem 是一个指向信号量的指针,pshared 是用于控制信号量的作用范围,value 是信号量的初值。

如果 pshared 的值为 0,则该信号量只能在当前进程中使用。否则,该信号量将在多个进程间共享。

sem_wait()
int sem_wait(sem_t *sem);

这个函数用于将信号量的值减 1。如果减完之后信号量的值变为了负数,表示当前资源正在被占用,那么调用进程就会被阻塞。

sem_post()
int sem_post(sem_t *sem);

这个函数用于将信号量的值加 1。如果此时有进程被阻塞等待该资源,则此时该进程将被唤醒。

信号量的应用场景

信号量的经典应用场景包括生产者-消费者问题以及读者-写者问题。

生产者-消费者问题

在该问题中,我们需要实现两个进程:一个生产者进程和一个消费者进程。它们需要访问同一个共享队列来进行数据传输。当队列为空时,消费者进程需要等待生产者进程生产数据;当队列已满时,生产者进程需要等待消费者进程消费数据。

下面是一个使用信号量解决生产者-消费者问题的代码片段:

#include <stdio.h>
#include <semaphore.h>
#include <pthread.h>

#define MAX_ITEM 10

int queue[MAX_ITEM];
int front, rear;
sem_t mutex, empty, full;

void init() {
    sem_init(&mutex, 0, 1);
    sem_init(&empty, 0, MAX_ITEM);
    sem_init(&full, 0, 0);
    front = rear = 0;
}

void put(int item) {
    sem_wait(&empty);
    sem_wait(&mutex);
    queue[rear] = item;
    rear = (rear + 1) % MAX_ITEM;
    sem_post(&mutex);
    sem_post(&full);
}

int get() {
    int item;
    sem_wait(&full);
    sem_wait(&mutex);
    item = queue[front];
    front = (front + 1) % MAX_ITEM;
    sem_post(&mutex);
    sem_post(&empty);
    return item;
}

void *producer(void *arg) {
    int item;
    while (1) {
        item = rand();
        printf("producing item %d\n", item);
        put(item);
    }
}

void *consumer(void *arg) {
    int item;
    while (1) {
        item = get();
        printf("consuming item %d\n", item);
    }
}

int main() {
    init();
    pthread_t p1, p2, p3, c1, c2, c3;
    pthread_create(&p1, NULL, producer, NULL);
    pthread_create(&p2, NULL, producer, NULL);
    pthread_create(&p3, NULL, producer, NULL);
    pthread_create(&c1, NULL, consumer, NULL);
    pthread_create(&c2, NULL, consumer, NULL);
    pthread_create(&c3, NULL, consumer, NULL);
    pthread_join(p1, NULL);
    pthread_join(p2, NULL);
    pthread_join(p3, NULL);
    pthread_join(c1, NULL);
    pthread_join(c2, NULL);
    pthread_join(c3, NULL);
    return 0;
}

在上面的代码中,我们使用了三个信号量:mutex 用于实现互斥访问共享队列,empty 用于表示队列中空余的位置数量,full 则用于表示队列中已经有的元素数量。

读者-写者问题

在该问题中,我们需要实现两个进程:一个读者进程和一个写者进程。它们需要访问同一个共享文件,其中写者进程将向文件中写入数据,读者进程则需要从文件中读取数据。

下面是一个使用信号量解决读者-写者问题的代码片段:

#include <stdio.h>
#include <semaphore.h>
#include <pthread.h>

#define MAX_READERS 10

int g_count = 0;
sem_t g_rmutex, g_wmutex, g_readTry, g_resource;

void init() {
    sem_init(&g_rmutex, 0, 1);
    sem_init(&g_wmutex, 0, 1);
    sem_init(&g_readTry, 0, 1);
    sem_init(&g_resource, 0, 1);
}

void *writer(void *arg) {
    int id = *(int*)arg;
    while (1) {
        sem_wait(&g_wmutex);
        g_count++;
        if (g_count == 1)
            sem_wait(&g_resource);
        sem_post(&g_wmutex);
        printf("writer %d is writing\n", id);
        // write to file...
        sem_wait(&g_wmutex);
        g_count--;
        if (g_count == 0)
            sem_post(&g_resource);
        sem_post(&g_wmutex);
    }
}

void *reader(void *arg) {
    int id = *(int*)arg;
    while (1) {
        sem_wait(&g_readTry);
        sem_wait(&g_rmutex);
        g_count++;
        if (g_count == 1)
            sem_wait(&g_resource);
        sem_post(&g_rmutex);
        sem_post(&g_readTry);
        printf("reader %d is reading\n", id);
        // read from file...
        sem_wait(&g_rmutex);
        g_count--;
        if (g_count == 0)
            sem_post(&g_resource);
        sem_post(&g_rmutex);
    }
}

int main() {
    init();
    pthread_t writers[3], readers[3];
    int ids[3] = {1, 2, 3};
    for (int i = 0; i < 3; i++) {
        pthread_create(&writers[i], NULL, writer, &ids[i]);
        pthread_create(&readers[i], NULL, reader, &ids[i]);
    }
    for (int i = 0; i < 3; i++) {
        pthread_join(writers[i], NULL);
        pthread_join(readers[i], NULL);
    }
    return 0;
}

在上面的代码中,我们使用了四个信号量:g_rmutexg_wmutex 用于实现读者和写者之间的互斥访问;g_readTry 用于实现读者间的互斥访问,g_resource 则用于表示共享文件是否正在被写入。