📜  信号量同步的经典问题(1)

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

信号量同步的经典问题介绍

在多线程编程中,有时需要保证多个线程之间的同步和互斥,避免出现竞态条件等问题。而信号量同步就是一种常见的手段。它通过对共享资源的访问进行控制,来保证多个线程之间的同步和互斥。

信号量的定义

信号量是一个计数器,用来控制多个线程对共享资源的访问。当线程想要使用共享资源时,它首先要尝试获取信号量,如果信号量的值大于0,则线程可以使用共享资源,同时将信号量的值减1;如果信号量的值等于0,则线程会阻塞等待,直到有其他线程释放了信号量,使得它可以获取到信号量。

当线程使用完共享资源后,它需要将信号量的值加1,以表示它已经释放了该资源。

信号量同步的经典问题

信号量同步的经典问题有多种,其中较为典型的问题是生产者消费者问题和哲学家就餐问题。

生产者消费者问题

生产者消费者问题是指一个共享缓冲区,生产者向其中放入数据,消费者从其中取出数据。由于缓冲区的空间有限,当生产者向其中放入数据时,如果缓冲区已满,则需要等待消费者取走数据;当消费者从中取出数据时,如果缓冲区为空,则需要等待生产者放入数据。

使用信号量可以很好地解决生产者消费者问题,具体方式是定义两个共享变量:一个表示缓冲区中可以放置数据的数量,另一个表示缓冲区中已经放置了数据的数量。当生产者想要向缓冲区放入数据时,要先尝试获取缓冲区可用的空间,如果可用的空间为0,则意味着缓冲区已满,生产者就需要等待;如果可用空间不为0,则生产者可以将数据放入缓冲区,并将已放置数据的数量加1。当消费者想要从缓冲区取出数据时,要先尝试获取缓冲区中已经放置了数据的数量,如果已放置的数据数量为0,则表示缓冲区为空,消费者就需要等待;如果已放置的数据数量不为0,则消费者可以从缓冲区取出数据,并将已放置数据的数量减1。

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

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

#define BUFFER_SIZE 10

int buffer[BUFFER_SIZE];
int in = 0;
int out = 0;

sem_t empty;
sem_t full;
pthread_mutex_t lock;

void* producer(void* arg) {
    for (int i = 0; i < 100; ++i) {
        sem_wait(&empty);
        pthread_mutex_lock(&lock);
        buffer[in] = i;
        in = (in + 1) % BUFFER_SIZE;
        pthread_mutex_unlock(&lock);
        sem_post(&full);
    }
    return NULL;
}

void* consumer(void* arg) {
    for (int i = 0; i < 100; ++i) {
        sem_wait(&full);
        pthread_mutex_lock(&lock);
        int data = buffer[out];
        out = (out + 1) % BUFFER_SIZE;
        pthread_mutex_unlock(&lock);
        sem_post(&empty);
        printf("Consumer got data %d\n", data);
    }
    return NULL;
}

int main() {
    sem_init(&empty, 0, BUFFER_SIZE);
    sem_init(&full, 0, 0);
    pthread_mutex_init(&lock, NULL);

    pthread_t producer_thread;
    pthread_t consumer_thread;

    pthread_create(&producer_thread, NULL, producer, NULL);
    pthread_create(&consumer_thread, NULL, consumer, NULL);

    pthread_join(producer_thread, NULL);
    pthread_join(consumer_thread, NULL);

    pthread_mutex_destroy(&lock);
    sem_destroy(&full);
    sem_destroy(&empty);

    return 0;
}

哲学家就餐问题

哲学家就餐问题是指五个哲学家坐在围着一张圆桌的五张椅子上,桌子上有五份面条和五个叉子,每个哲学家吃面条需要用到左右两个叉子。由于叉子是共享资源,如果哲学家同时拿起左右两个叉子,就会导致死锁。

使用信号量可以很好地解决哲学家就餐问题。具体方式是定义五个信号量,每个信号量表示一个叉子是否可用。当哲学家想要吃饭时,它需要先尝试获取左右两个叉子,如果两个叉子都可用,则哲学家可以拿起叉子吃饭;否则,哲学家需要释放它已经获取的叉子,并等待其他哲学家放回叉子后再尝试获取。

下面是一个使用信号量解决哲学家就餐问题的示例代码:

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

#define N 5

sem_t forks[N];
sem_t waiter;

void* philosopher(void* arg) {
    int i = *(int*) arg;
    int left = i;
    int right = (i + 1) % N;

    while (1) {
        printf("Philosopher %d is thinking\n", i);
        sem_wait(&waiter);
        sem_wait(&forks[left]);
        sem_wait(&forks[right]);
        printf("Philosopher %d is eating\n", i);
        sem_post(&forks[right]);
        sem_post(&forks[left]);
        sem_post(&waiter);
    }

    return NULL;
}

int main() {
    sem_init(&waiter, 0, N - 1);
    for (int i = 0; i < N; ++i) {
        sem_init(&forks[i], 0, 1);
    }

    pthread_t philosophers[N];
    int ids[N];
    for (int i = 0; i < N; ++i) {
        ids[i] = i;
        pthread_create(&philosophers[i], NULL, philosopher, &ids[i]);
    }

    for (int i = 0; i < N; ++i) {
        pthread_join(philosophers[i], NULL);
    }

    sem_destroy(&waiter);
    for (int i = 0; i < N; ++i) {
        sem_destroy(&forks[i]);
    }

    return 0;
}
结论

信号量同步是多线程编程中常用的一种技术,可以很好地保证多个线程之间的同步和互斥。在实际编程中,需要仔细考虑信号量的数量和控制流程,避免出现死锁等问题。