哲学家干饭问题 C++

哲学家干饭问题 C++

img

哲学家就餐问题可以这样表述,假设有五位哲学家围坐在一张圆形餐桌旁,做以下两件事情之一:吃饭,或者思考。吃东西的时候,他们就停止思考,思考的时候也停止吃东西。餐桌上有五碗意大利面,每位哲学家之间各有一支餐叉。因为用一支餐叉很难吃到意大利面,所以假设哲学家必须用两支餐叉吃东西。他们只能使用自己左右手边的那两支餐叉。哲学家就餐问题有时也用米饭和五根筷子而不是意大利面和餐叉来描述,因为吃米饭必须用两根筷子。

这个问题不考虑意大利面有多少,也不考虑哲学家的胃有多大。假设两者都是无限大。

问题在于如何设计一套规则,使得在哲学家们在完全不交谈,也就是无法知道其他人可能在什么时候要吃饭或者思考的情况下,可以在这两种状态下永远交替下去。

使用忙等待获取资源

#include <iostream>
#include <thread>
#include <atomic>
#include <atomic>
#include <format>
#include <list>
#include <mutex>
#include <shared_mutex>
#include <execution>
#include <map>
#include <future>
#include <future>
#include <iostream>
#include <thread>
#include <utility>
#include <random>


using namespace std;

std::random_device rd_device;
std::mt19937_64 rd_engine(rd_device());

//生成随机数
int generate_random_number(int min, int max)
{
	std::uniform_int_distribution<int> dist(min, max);
	return dist(rd_engine);
}

void lock(std::atomic<int>& resource_number)
{
	while (resource_number);
	//忙等待
	resource_number = 1;
}


void unlock(std::atomic<int>& resource_number)
{
	resource_number = 0;
}

void feast_think(int n, std::atomic<int>& a, std::atomic<int>& b)
{
	while (true)
	{
		int duration = generate_random_number(1000, 1500);
		std::cout << std::format("{} 思考{}ms \n", n, duration);
		std::this_thread::sleep_for(std::chrono::milliseconds(duration)); // 思考

		lock(a);
		std::cout << std::format("{} 获取a筷子\n", n);
		std::this_thread::sleep_for(std::chrono::milliseconds(1000));

		lock(b);
		std::cout << std::format("{} 获取b筷子\n", n);

		duration = generate_random_number(1000, 1500);
		std::cout << std::format("{} 干饭时间:{} ms \n", n, duration);

		std::this_thread::sleep_for(std::chrono::milliseconds(duration));

		unlock(b);
		unlock(a);
		std::cout << "\n\n";
	}
}

int main(int argc, char* argv[])
{
	std::atomic<int> m1{0}, m2{0}, m3{0}, m4{0};

	std::thread t1{[&]() { feast_think(1, m1, m2); }};
	std::thread t2{[&]() { feast_think(2, m2, m3); }};
	std::thread t3{[&]() { feast_think(3, m3, m4); }};
	std::thread t4{[&]() { feast_think(4, m1, m4); }};

	t1.join();
	t2.join();
	t3.join();
	t4.join();
}

image-20220918133938698

我们使用lock|unlock获取和释放资源, 但是在这之间,线程调度可能会导致线程切换,出现一些难以发现的问题

优化使用忙等待获取资源

std::random_device rd_device;
std::mt19937_64 rd_engine(rd_device());

//生成随机数
int generate_random_number(int min, int max)
{
	std::uniform_int_distribution<int> dist(min, max);
	return dist(rd_engine);
}

void lock(std::atomic_flag& resource_flag)
{
	while (resource_flag.test_and_set());
	//忙等待
}


void unlock(std::atomic_flag& resource_flag)
{
	resource_flag.clear();
}

void feast_think(int n, std::atomic_flag& a, std::atomic_flag& b)
{
	while (true)
	{
		int duration = generate_random_number(100, 200);
		std::cout << std::format("{} 思考{}ms \n", n, duration);
		std::this_thread::sleep_for(std::chrono::milliseconds(duration)); // 思考

		lock(a);
		std::cout << std::format("{} 获取a筷子\n", n);
		std::this_thread::sleep_for(std::chrono::milliseconds(100));

		lock(b);
		std::cout << std::format("{} 获取b筷子\n", n);

		duration = generate_random_number(100, 200);
		std::cout << std::format("{} 干饭时间:{} ms \n", n, duration);

		std::this_thread::sleep_for(std::chrono::milliseconds(duration));

		unlock(b);
		unlock(a);
		std::cout << "\n\n";
	}
}

int main(int argc, char* argv[])
{
	std::atomic_flag m1, m2, m3, m4;

	std::thread t1{[&]() { feast_think(1, m1, m2); }};
	std::thread t2{[&]() { feast_think(2, m2, m3); }};
	std::thread t3{[&]() { feast_think(3, m3, m4); }};
	std::thread t4{[&]() { feast_think(4, m1, m4); }};

	t1.join();
	t2.join();
	t3.join();
	t4.join();
}

lock中使用循环浪费了大量的CPU,可以使用sleep_for短暂的暂停循环来节约CPU

image-20220918134956329

降低CPU占用率

添加

while (resource_flag.test_and_set())
	{
		std::this_thread::sleep_for(std::chrono::milliseconds(8));
	}

image-20220918135104626

CPU占用几乎为零

使用std::mutex

关于自旋锁与互斥量的区别:Waiting Thread - an overview | ScienceDirect Topics

简单说:自旋锁等待另一个线程,互斥量等待系统调度,所以互斥量不会占用大量的CPU周期,自旋锁会占用大量的CPU周期

void feast_think(int n, std::mutex& a, std::mutex& b)
{
	while (true)
	{
		int duration = generate_random_number(100, 200);
		std::cout << std::format("{} 思考{}ms \n", n, duration);
		std::this_thread::sleep_for(std::chrono::milliseconds(duration)); // 思考

		a.lock();
		std::cout << std::format("{} 获取a筷子\n", n);
		std::this_thread::sleep_for(std::chrono::milliseconds(100));

		b.lock();
		std::cout << std::format("{} 获取b筷子\n", n);

		duration = generate_random_number(100, 200);
		std::cout << std::format("{} 干饭时间:{} ms \n", n, duration);

		std::this_thread::sleep_for(std::chrono::milliseconds(duration));

		b.unlock();
		a.unlock();
		std::cout << "\n\n";
	}
}

int main(int argc, char* argv[])
{
	std::mutex m1, m2, m3, m4;

	std::thread t1{[&]() { feast_think(1, m1, m2); }};
	std::thread t2{[&]() { feast_think(2, m2, m3); }};
	std::thread t3{[&]() { feast_think(3, m3, m4); }};
	std::thread t4{[&]() { feast_think(4, m1, m4); }};

	t1.join();
	t2.join();
	t3.join();
	t4.join();
}

使用std::unique_lock以及std::defer_lock标志

我们可以使用一步操作,同时获取两个互斥量,这样就不会有死锁的风险

为了保证输出同步,添加一个mutex mo

using namespace std;

std::random_device rd_device;
std::mt19937_64 rd_engine(rd_device());

//生成随机数
int generate_random_number(int min, int max)
{
	std::uniform_int_distribution<int> dist(min, max);
	return dist(rd_engine);
}

std::mutex mo;

void feast_think(int n, std::mutex& a, std::mutex& b)
{
	while (true)
	{
		int duration = generate_random_number(1000, 2000);

		{
			std::lock_guard<std::mutex> g(mo);
			std::cout << std::format("{} 思考{}ms \n", n, duration);
		}

		std::this_thread::sleep_for(std::chrono::milliseconds(duration)); // 思考

		std::unique_lock<std::mutex> ga(a, std::defer_lock);

		{
			std::lock_guard<std::mutex> g(mo);
			std::cout << std::format("{} 获取a筷子\n", n);
		}


		std::this_thread::sleep_for(std::chrono::milliseconds(1000));

		std::unique_lock<std::mutex> gb(b, std::defer_lock);
		std::lock(ga, gb);
		{
			std::lock_guard<std::mutex> g(mo);
			std::cout << std::format("{} 获取b筷子\n", n);
		}

		duration = generate_random_number(1000, 2000);
		{
			std::lock_guard<std::mutex> g(mo);
			std::cout << std::format("{} 干饭时间:{} ms \n", n, duration);
		}

		std::this_thread::sleep_for(std::chrono::milliseconds(duration));


		std::cout << "\n\n";
	}
}

int main(int argc, char* argv[])
{
	std::mutex m1, m2, m3, m4;

	std::thread t1{[&]() { feast_think(1, m1, m2); }};
	std::thread t2{[&]() { feast_think(2, m2, m3); }};
	std::thread t3{[&]() { feast_think(3, m3, m4); }};
	std::thread t4{[&]() { feast_think(4, m1, m4); }};

	t1.join();
	t2.join();
	t3.join();
	t4.join();
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值