📅  最后修改于: 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;
}
信号量同步是多线程编程中常用的一种技术,可以很好地保证多个线程之间的同步和互斥。在实际编程中,需要仔细考虑信号量的数量和控制流程,避免出现死锁等问题。