C++标准线程库之哲学家就餐问题

哲学家就餐问题是多线程中著名的一个问题,经过前面三章的学习,可以使用多线程来模拟下这个问题了。
问题是这样的,有5个哲学家围着一个小圆餐桌坐了下来,但是桌上只有5根筷子(注意是根),每个哲学家只有全抢到左右手边的筷子才能吃东西。抢到2根筷子的哲学家过1秒后把筷子放回原位置,继续游戏。当只抢到1根,另一手的筷子被其他哲学家抢走时,就放下手中的筷子。
为了简化问题,所有哲学家都是先抢左手,在抢右手的,有兴趣可以自己去实现随机版的,差不多。
C++标准线程库之哲学家就餐问题_第1张图片

1. 问题分析

  • 首先,每个哲学家都应该是一个线程;
  • 其次,筷子是被共享的资源,所有每个筷子都应该被互斥量保护;
  • 很明显,1个互斥量是不够保护5个资源的,至少需要5个;
  • 因为需要判断能否上锁,互斥量的lock不能满足要求,而try_lock是可以的;
  • 吃完等待1秒继续。

2. 代码实现

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

std::mutex outputMutex;

class Chopsticks
{
    int _i;
    std::mutex _mutex;

public:
    explicit Chopsticks() : _i(0) {}
    void seti(int i) { _i = i; }

    friend std::ostream& operator <<(std::ostream& out, const Chopsticks& chopsticks);
	
	// 使用筷子就是上锁
    bool use() throw(std::system_error)
    {
        try {
            return _mutex.try_lock();
        }
        catch(std::system_error& e) {
            throw e;
        }
    }
	// 放下筷子就是解锁
    void push_down() throw(std::system_error)
    {
        try {
            return _mutex.unlock();
        }
        catch(std::system_error& e) {
            throw e;
        }
    }
};

std::ostream& operator <<(std::ostream& out, const Chopsticks& chopsticks)
{
    out << " 筷子: " << chopsticks._i << " ";
    return out;
}

class Philosopher
{
    int _i;
	// 拿某个筷子
    bool _take(Chopsticks& chopsticks)
    {
        if(chopsticks.use()) {
            std::lock_guard<std::mutex> lock(outputMutex);
            std::cout << "哲学家: " << _i << "拿到" << chopsticks << std::endl;
            fflush(stdout);
            return true;
        }
        else {
            std::lock_guard<std::mutex> lock(outputMutex);
            std::cout << "抢夺失败, 哲学家: " << _i << chopsticks << std::endl;
            fflush(stdout);
            return false;
        }
    }
	// 抢夺
    void _seizure(Chopsticks& lc, Chopsticks& rc)
    {
        while(true)
        {
            {
                std::lock_guard<std::mutex> lock(outputMutex);
                std::cout << "哲学家: " << _i << " 开始抢筷子..." << std::endl;
                fflush(stdout);
            }
			// 先拿左边,在那右边
            if(_take(lc)) {
                if(_take(rc)) {
                    std::lock_guard<std::mutex> lock(outputMutex);
                    std::cout << "哲学家: " << _i << "吃饭" << std::endl;
                    std::cout << "哲学家: " << _i << "放下" << rc << std::endl;
                    fflush(stdout);
                    rc.push_down();
                }

                std::lock_guard<std::mutex> lock(outputMutex);
                std::cout << "哲学家: " << _i << "放下" << lc << std::endl;
                fflush(stdout);
                lc.push_down();
            }
			// 休息1s
            std::this_thread::sleep_for(std::chrono::duration<double>(1));
        }
    }

public:
    explicit Philosopher() : _i(0) {}
    void seti(int i) { _i = i; }
	// 可调用的	
    void operator() (Chopsticks& lc, Chopsticks& rc)
    {
        _seizure(lc, rc);
    }
};

int main()
{
	// 5个哲学家,5个筷子	
	std::vector<Philosopher> philosophers(5);
    std::vector<Chopsticks> chopsticks(5);
    std::vector<std::thread> threads;
	
	// 设置序号	
    for(int i = 0; i < 5; ++ i) {
        philosophers.at(i).seti(i + 1);
        chopsticks.at(i).seti(i + 1);
    }
	// 开启线程	
    for(int i = 0; i < 4; ++ i) {
        std::thread thread(philosophers.at(i), std::ref(chopsticks.at(i)), std::ref(chopsticks.at(i + 1)));
        threads.push_back(std::move(thread));
    }
    std::thread thread(philosophers.at(4), std::ref(chopsticks.at(4)), std::ref(chopsticks.at(0)));
    threads.push_back(std::move(thread));

    std::for_each(threads.begin(), threads.end(), [](std::thread& t) {
        t.join();
    });
    return 0;
}

C++标准线程库之哲学家就餐问题_第2张图片

3. 总结

虽然代码不多一百来行,但是其用到了线程创建,线程管理,互斥量等知识,细细品味能加深对线程的理解。

4. 相关系列

  • C++标准线程库之入门
  • C++标准线程库之当前线程管理
  • C++标准线程库之共享资源

你可能感兴趣的:(C++标准库)