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

传送门:

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

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

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

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

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

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

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

哲学家就餐问题

哲学家就餐问题(dining philosopher’s problem)是一个著名的并发问题,它由Dijkstra提出来并解决。这个问题之所以出名,是因为它很有趣,引人入胜,但其实用性却不强。可是,它的名气让我们在这里必须讲。实际上,你可能会在面试中遇到这一问题,假如老师没有提过,导致你们没有通过面试,你们会责怪操作系统老师的。因此,我们这里会讨论这一问题。假如你们因为这个问题得到工作,可以向操作系统老师发感谢信,或者发一些股票期权。

这个问题的基本情况是:假定有5位“哲学家”围着一个圆桌。每两位哲学家之间有一把餐叉(一共5把)。哲学家有时要思考一会,不需要餐叉;有时又要就餐。而一位哲学家只有同时拿到了左手边和右手边的两把餐叉,才能吃到东西。关于餐叉的竞争以及随之而来的同步问题,就是我们在并发编程中研究它的原因。

下面是每个哲学家的基本循环:

while(1)
{
	think();
	getforks();
	eat();
	putfork();
}

关键的挑战就是如何实现getforks()和putforks()函数,保证没有死锁,没有哲学家饿死,并且并发度更高(尽可能让更多哲学家同时吃东西)。

根据Downey的解决方案,我们会用一些辅助函数,帮助构建解决方案。它们是:

int left(int p)  { return p; }
int right(int p) { return (p + 1) % 5;}

如果哲学家p希望用左手边的叉子,他们就调用left§。类似地,右手边的叉子就用right§。模运算解决了最后一个哲学家(p = 4)右手边叉子的编号问题,就是餐叉0。

我们需要一些信号量来解决这个问题。假设需要5个,每个餐叉一个:sem_t forks[5]。

有问题的解决方案

我们开始第一次尝试。假设我们把每个信号量(在fork数组中)都用1初始化。同时假设每个哲学家知道自己的编号(p)。我们可以写出getforks()和putforks()函数。

void getforks()
{
	sem_wait(forks[left(p)]);
	sem_wait(forks[right(p)]);
}

void putforks()
{
	sem_post(forks[left(p)]);
	sem_post(forks[right(p)]);
}

这个(有问题的)解决方案背后的思路如下。为了拿到餐叉,我们依次获取每把餐叉的锁——先是左手边的,然后是右手边的。结束就餐时,释放掉锁。很简单,不是吗?但是,在这个例子中,简单是有问题的。你能看到问题吗?想一想。问题是死锁(deadlock)。假设每个哲学家都拿到了左手边的餐叉,他们每个都会阻塞住,并且一直等待另一个餐叉。具体来说,哲学家0拿到了餐叉0,哲学家1拿到了餐叉1,哲学家2拿到餐叉2,哲学家3拿到餐叉3,哲学家4拿到餐叉4。所有的餐叉都被占有了,所有的哲学家都阻塞着,并且等待另一个哲学家占有的餐叉。我们在后续章节会深入学习死锁,这里只要知道这个方案行不通就可以了。

一种方案:破除依赖

解决上述问题最简单的方法,就是修改某个或者某些哲学家的取餐叉顺序。事实上,Dijkstra自己也是这样解决的。具体来说,假定哲学家4(编写最大的一个)取餐叉的顺序不同。相应的代码如下:

void getforks()
{
	if(p == 4)
	{
		sem_wait(forks[right(p)]);		
		sem_wait(forks[left(p)]);	
	}
	else
	{
		sem_wait(forks[left(p)]);
		sem_wait(forks[right(p)]);		
	}
}

因为最后一个哲学家会尝试先拿右手边的餐叉,然后拿左手边,所以不会出现每个哲学家都拿着一个餐叉,卡住等待另一个的情况,等待循环被打破了。想想这个方案的后果,让你自己相信它有效。

源码实现:

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

using namespace std;

sem_t forks[5];

void think(int i)
{
    cout << "第" << i << "个哲学家正在思考" << endl;
    /* sleep(1); */
}

void eat(int i)
{
    cout << "第" << i << "个哲学家正在吃东西" << endl;
    /* sleep(2); */
}

int left(int p) {return p;}
int right(int p) {return (p + 1) % 5;}

void getforks(int i)
{
    sem_wait(&forks[left(i)]);
    cout << "第" << i << "个哲学家拿起左筷子" << endl;
    sem_wait(&forks[right(i)]);
    cout << "第" << i << "个哲学家拿起右筷子" << endl;
}

void putforks(int i)
{
    sem_post(&forks[left(i)]);
    cout << "第" << i << "个哲学家放下左筷子" << endl;
    sem_post(&forks[right(i)]);
    cout << "第" << i << "个哲学家放下右筷子" << endl;
}

void* philosopher_process_0(void* arg)
{
    int id = *(int*)arg;
    think(id);
    getforks(id);
    eat(id);
    putforks(id);
    pthread_exit(0);
}

void* philosopher_process_1(void* arg)
{
    int id = *(int*)arg;
    think(id);
    getforks(id);
    eat(id);
    putforks(id);
    pthread_exit(0);
}

void* philosopher_process_2(void* arg)
{
    int id = *(int*)arg;
    think(id);
    getforks(id);
    eat(id);
    putforks(id);
    pthread_exit(0);
}

void* philosopher_process_3(void* arg)
{
    int id = *(int*)arg;
    think(id);
    getforks(id);
    eat(id);
    putforks(id);
    pthread_exit(0);
}

void* philosopher_process_4(void* arg)
{
    int id = *(int*)arg;
    think(id);
    getforks(id);
    eat(id);
    putforks(id);
    pthread_exit(0);
}

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

    pthread_t philosopher[5];

    int id[5] = {0, 1, 2, 3, 4};
    
    pthread_create(&philosopher[0], nullptr, philosopher_process_0, &id[0]);
    pthread_create(&philosopher[1], nullptr, philosopher_process_1, &id[1]);
    pthread_create(&philosopher[2], nullptr, philosopher_process_2, &id[2]);
    pthread_create(&philosopher[3], nullptr, philosopher_process_3, &id[3]);
    pthread_create(&philosopher[4], nullptr, philosopher_process_4, &id[4]);

    pthread_join(philosopher[0], nullptr);
    pthread_join(philosopher[1], nullptr);
    pthread_join(philosopher[2], nullptr);
    pthread_join(philosopher[3], nullptr);
    pthread_join(philosopher[4], nullptr);

    return 0;
}

触发死锁是概率事件,实际运行结果:

4个哲学家正在思考
第4个哲学家拿起左筷子
第4个哲学家拿起右筷子
第4个哲学家正在吃东西
第4个哲学家放下左筷子
第4个哲学家放下右筷子
第3个哲学家正在思考
第3个哲学家拿起左筷子
第3个哲学家拿起右筷子
第3个哲学家正在吃东西
第3个哲学家放下左筷子
第3个哲学家放下右筷子
第2个哲学家正在思考
第2个哲学家拿起左筷子
第2个哲学家拿起右筷子
第2个哲学家正在吃东西
第2个哲学家放下左筷子
第2个哲学家放下右筷子
第1个哲学家正在思考
第1个哲学家拿起左筷子
第1个哲学家拿起右筷子
第1个哲学家正在吃东西
第1个哲学家放下左筷子
第1个哲学家放下右筷子
第0个哲学家正在思考
第0个哲学家拿起左筷子
第0个哲学家拿起右筷子
第0个哲学家正在吃东西
第0个哲学家放下左筷子
第0个哲学家放下右筷子

在摄影中,光源和曝光是两个关键因素,它们共同决定了照片的质量和视觉效果。以下是关于两者对比及如何影响相机拍摄效果的具体分析: --- ### 光源的影响 1. **定义** 光源是指照亮被摄物体的光线来源,包括自然光(如阳光)和人造光(如灯光、闪光灯)。不同光源会影响画面的颜色、亮度以及阴影的表现。 2. **色温作用** 色温以开尔文 (K) 为单位衡量光源颜色特性。低色温(约2000–3000 K)呈现暖色(橙黄色),色温(超过5000 K)则偏向冷色(蓝白色)。正确设置白平衡能够补偿不同光源带来的色彩偏差。 3. **方向性强度** - 直射光会产生鲜明的影子并增加纹理细节。 - 柔和散射光减少硬边阴影,适合人像或静物摄影。 - 强烈光源可能造成过曝区域,而弱光源需要更长快门速度或更ISO值来捕捉足够信息。 4. **动态范围限制** 当场景中有极大亮暗差异时,传感器无法同时记录所有层次细节。此时需借助HDR技术或多张合成解决这一问题。 --- ### 曝光的作用 1. **基本原理** 曝光指感光元件接收多少光线的过程,由三要素组成:光圈大小(f-stop),快门时间(shutter speed),以及感光度(ISO)。这三者相互关联构成所谓的“曝光三角”。 2. **过度/不足曝光后果** 如果曝光过多,则明亮部分失去细节;反之若欠曝,则黑暗部位变得死黑无细节。理想状态应保持主体清晰可见且保留尽可能多的信息层阶。 3. **手动调整技巧** - 加大光圈可以让更多光线进入但降低景深。 - 减慢快门能延长进光时间但也容易引入运动模糊。 - 提升ISO虽增强敏感度却伴随噪声增多的风险。 4. **测光模式选择** 不同测光方式适用于各类情况: - 点测光专注于小面积精确控制; - 局部测光兼顾局部整体关系; - 平均测光综合考虑整个画面平均亮度水平。 --- ### 综合应用实例 假设在一个室内环境中拍摄人物肖像照: - 若仅依赖现有微弱环境照明,则必须适当提ISO或将快门放慢才能保证正常曝光,但这可能导致噪点增加或者手抖导致的画面不稳。 - 反之如果加入辅助人工补光装置比如柔光箱,则即使维持较低灵敏度设定也能获取良好结果,并且还能塑造更具立体感的脸部轮廓线条。 因此,在实际创作过程中往往要结合具体情况灵活运用这两种手段达到最佳表现力。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值