进程同步与互斥——生产者/消费者(有界缓冲区)问题源码实现

传送门:

进程同步与互斥——信号量(实现锁、条件变量)

进程同步与互斥——哲学家就餐问题源码实现(dining philosopher’s problem)

进程同步与互斥——读者/写者问题源码实现(reader-writer lock)

进程同步与互斥——生产者/消费者(有界缓冲区)问题源码实现

进程同步与互斥——吸烟者问题源码实现(cigarette smoker’s problem)

进程同步与互斥——理发师问题源码实现(sleeping barber problem)

进程同步与互斥——相关问题汇总(源码+伪代码)

摘自:操作系统导论

第一次尝试

第一次尝试解决该问题时,我们用两个信号量empty和full分别表示缓存区空或者满。put()和get()函数,下面是我们尝试解决生产者/消费者问题的代码。

#define MAX 10

int buffer[MAX];
int fill = 0;
int use = 0;

void put(int value)
{
	buffer[fill] = value;           //line f1
	fill = (fill + 1) % MAX;
}

int get()

{
	int tmp = buffer[use];
	use = (use + 1) % MAX;
	return tmp;
}

sem_t empty;
sem_t full;

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

void *consumer(void *arg)
{
	int i, tmp = 0;
	while(tmp != -1)
	{
		sem_wait(&full)
		tmp = get();
		sem_post(&empty);
		printf("%d\n", tmp);
	}
}

int main()
{
	sem_init(&empty, 0, MAX);    // MAX buffers are empty to begin with...
	sem_init(&full, 0, 0);       // ...and 0 are full
}

你可以用更多的线程来尝试这个例子(即多个生产者和多个消费者)。它应该仍然正常运行。

我们现在假设MAX大于1(比如MAX=10)。对于这个例子,假定有多个生产者,多个消费者。现在就有问题了:竞态条件。你能够发现是哪里产生的吗?(花点时间找一下)如果没有发现,不妨仔细观察put()和get()的代码。

好,我们来理解该问题。假设两个生产者(Pa和Pb)几乎同时调用put()。当Pa先运行,在f1行先加入第一条数据(fill=0),假设Pa在将fill计数器更新为1之前被中断,Pb开始运行,也在f1行给缓冲区的0位置加入一条数据,这意味着那里的老数据被覆盖!这可不行,我们不能让生产者的数据丢失。

解决方案:增加互斥

你可以看到,这里忘了互斥。向缓冲区加入元素和增加缓冲区的索引是临界区,需要小心保护起来。所以,我们使用二值信号量来增加锁。

sem_t empty;
sem_t full;
sem_t mutex;

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

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

int main()
{
	sem_init(&empty, 0, MAX);    // MAX buffers are empty to begin with...
	sem_init(&full, 0, 0);       // ...and 0 are full
	sem_init(&mutex, 0, 1);      // mutex = 1, because it is a lock 
}

现在我们给整个put()/get()部分都增加了锁。这似乎是正确的思路,但仍然有问题。为什么?死锁。为什么会发生死锁?考虑一下,尝试找出一个死锁的场景。必须以怎样的步骤执行,会导致程序死锁?

避免死锁

好,既然你想出来了,下面是答案。假设有两个线程,一个生产者和一个消费者。消费者首先运行,获得锁,然后对full信号量执行sem_wait() 。因为还没有数据,所以消费者阻塞,让出CPU。但是,重要的是,此时消费者仍然持有锁。

然后生产者运行。假如生产者能够运行,它就能生产数据并唤醒消费者线程。遗憾的是,它首先对二值互斥信号量调用sem_wait()。锁已经被持有,因此生产者也被卡住。

这里出现了一个循环等待。消费者持有互斥量,等待在full信号量上。生产者可以发送full信号,却在等待互斥量。因此,生产者和消费者互相等待对方——典型的死锁。

最后,可行的方案

要解决这个问题,只需减少锁的作用域。可以看到,我们把获取和释放互斥量的操作调整为紧挨着临界区,把full、empty的唤醒和等待操作调整到锁外面。结果得到了简单而有效的有界缓冲区,多线程程序的常用模式。现在理解,将来使用。未来的岁月中,你会感谢我们的。至少在期末考试遇到这个问题时,你会感谢我们。

sem_t empty;
sem_t full;
sem_t mutex;

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

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

int main()
{
	sem_init(&empty, 0, MAX);    // MAX buffers are empty to begin with...
	sem_init(&full, 0, 0);       // ...and 0 are full
	sem_init(&mutex, 0, 1);      // mutex = 1, because it is a lock 
}

源码实现:

#include <semaphore.h>
#include <pthread.h>
#include <iostream>
#include <unistd.h>
#include <time.h>

using namespace std;

#define MAX 10

int buffer[MAX];
int _fill = 0;
int use = 0;

void put(int value)
{
    buffer[_fill] = value;
    _fill = (_fill + 1) % MAX;
}

int get()
{
    int tmp = buffer[use];
    use = (use + 1) % MAX;
    return tmp;
}

sem_t _empty;
sem_t full;
sem_t mutexlock;

#define loops 10

void *producer(void* arg)
{
    for(int i = 0; i < loops; i++)
    {
        sem_wait(&_empty);
        sem_wait(&mutexlock);
        put(i);
        sem_post(&mutexlock);
        sem_post(&full);
        cout << "producer put " << i << endl;
        sleep(1);
    }

    pthread_exit(0);
}

void *consumer(void* arg)
{
    for(int i = 0; i < loops; i++)
    {
        int tmp;
        sem_wait(&full);
        sem_wait(&mutexlock);
        tmp = get();
        sem_post(&mutexlock);
        sem_post(&_empty);
        cout << "consumer get " << tmp << endl;
        sleep(2);
    }
    
    pthread_exit(0);
}

int main()
{
    sem_init(&_empty, 0, MAX);
    sem_init(&full, 0, 0);
    sem_init(&mutexlock, 0, 1);

    pthread_t pth1;
    pthread_t pth2;

    pthread_create(&pth1, nullptr, producer, nullptr);
    pthread_create(&pth2, nullptr, consumer, nullptr);

    pthread_join(pth1, nullptr);
    pthread_join(pth2, nullptr);

    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值