【问题标题】:C++ Threading IssuesC++ 线程问题
【发布时间】:2022-01-08 20:39:11
【问题描述】:

我正在尝试解决以下问题:“这个类比是基于一个假设的理发店有一个理发师。理发师在剪裁室有一把理发椅和一个包含许多椅子的等候室。当理发师剪完一个顾客的头发,他把顾客打发走了,去候车室看看有没有其他人在等,如果有,他把其中一个人带回椅子上剪头发。如果没有,他回到椅子上睡去。

每位顾客到达时都会看看理发师在做什么。如果理发师在睡觉,顾客会叫醒他并坐在剪裁室的椅子上。如果理发师在剪头发,顾客会留在等候室。如果候诊室里有空椅子,顾客就坐在上面等待轮到他们。如果没有空闲的椅子,顾客就会离开。 "

我的整个代码如下:

// Example program
#include <iostream>
#include <string>
#include <mutex>
#include <thread>
#include <chrono>
#include <atomic>
#include <deque>
#include <vector>
#include <condition_variable>
using namespace std;

const unsigned int NUM_CHAIRS = 2;
const unsigned int NUM_CUSTOMERS = 1;

// In a barbershop, customers are coming in and there is only one barber. If the barber is cutting a
// customers hair, the customer will wait. There are only a limited amount of chairs for the customer
// to wait on. If the chairs are full, then the customer will leave and try again later.
    
struct Barbershop
{
    public:
        Barbershop(): num_waiting(0){};
        condition_variable cv; // Acts like a receptionist
        atomic<int> num_waiting; // Number of customers waiting on chairs
        mutex cout_mtx;

};


class Barber
{
    public:
        Barber(Barbershop& shop): mem_shop(shop), hislife(&Barber::work, this){};
        void work()
        {
            unique_lock<mutex> lk(b_mu);
            if(mem_shop.num_waiting == 0)
            {
                mem_shop.cout_mtx.lock();
                cout << "Barber sleeping...\n";
                mem_shop.cout_mtx.unlock();
            }
            mem_shop.cv.wait(lk, [this]{return mem_shop.num_waiting > 0;}); // []{return mem_shop.num_waiting}
            mem_shop.cout_mtx.lock();
            cout << "Time to cut some hair!\n";
            mem_shop.cout_mtx.unlock();
            lk.unlock();
            mem_shop.cv.notify_one();
            this_thread::sleep_for(chrono::milliseconds(100));
            mem_shop.num_waiting--;
        };
        ~Barber()
        {
            hislife.join();
        }
        int jn;
        mutex b_mu;
        Barbershop& mem_shop;
        thread hislife;
        
};

class Customer
{
    public:
        Customer(Barbershop& shop, int name_id): mem_shop(shop), name(name_id), hislife(&Customer::tryGetHaircut, this){};
        void tryGetHaircut()
        {
            if(mem_shop.num_waiting == NUM_CHAIRS)
            {
                mem_shop.cout_mtx.lock();
                cout << name <<"'s wait was too long. Leaving!\n";
                mem_shop.cout_mtx.unlock();
            }
            else
            {
                mem_shop.num_waiting++;
                std::unique_lock<mutex> lk(c_mu);
                mem_shop.cv.wait(lk);
                mem_shop.cout_mtx.lock();
                cout << name << " is getting haircut!\n";
                mem_shop.cout_mtx.unlock();
            }
        };
        ~Customer()
        {
            hislife.join();
        }
        mutex c_mu;
        Barbershop& mem_shop;
        thread hislife;
        unsigned int name;

};

int main()
{
    Barbershop mybarbershop;
    Barber dan();
    vector<Customer*> customers;
    customers.reserve(NUM_CUSTOMERS-1);
    for(unsigned int i=0; i<NUM_CUSTOMERS; i++)
    {
        customers.push_back(new Customer(mybarbershop, i+1));
    }

    for(auto s : customers)
    {
        delete s;
    }
    cout << "Program Finished!\n";
    return 0;
}

但是,我的代码甚至没有进入 Barbers 线程,并且似乎被锁定在条件变量上。在解决这个问题时,我在逻辑/语法方面有什么明显的问题吗?

我仍然是初学者,因此我们将不胜感激任何有关该主题的帮助。

【问题讨论】:

  • OT: std::vector&lt;std::unique_ptr&lt;Customer&gt;&gt; customers; 可能是更好的选择。
  • 你也根本不需要cout_mtx。写入std::cout 本身保证是线程安全的。参见,例如,stackoverflow.com/q/6374264/580083。在另一个锁定的互斥锁下获取互斥锁会产生死锁。
  • @DanielLangr 它是线程安全的,但是由于同时刷新缓冲区,您可能会混淆行。
  • @DanielLangr 感谢您的回复!我会阅读链接并尝试一下。您是否也碰巧尝试过运行代码?
  • @JorgeBellon AFAIK,甚至 endlflush 仅在库级别解析刷新。操作系统仍然可以使用一些内部缓冲区,这些缓冲区通常无法从用户空间进行控制。

标签: c++ multithreading


【解决方案1】:

你有几个问题:

  1. if(mem_shop.num_waiting == NUM_CHAIRS)mem_shop.num_waiting++ 之间存在数据竞争,不受锁定保护。因此,两个顾客可以同时加入理发店,看到一把椅子,并且都使用同一张椅子。因此,当队列太长时,num_waiting &gt; NUM_CHAIRS 和更多的客户端可能会加入队列。您需要使用原子比较和交换,或者只使用锁保护变量访问。

  2. mem_shop.cv.wait(lk, [this]{return mem_shop.num_waiting &gt; 0;}); 让理发师在有人等的时候等。条件不应该是相反的(如果没有人在等待,则等待)? [this]{return !mem_shop.num_waiting;}。还有一个访问num_waiting 的数据竞争,因为理发师和客户没有访问同一个锁。要么将num_waiting 变成原子,要么让理发师和客户通过理发店共享互斥锁(因此访问计数和条件变量都使用同一个互斥锁进行保护)。

注意:num_waiting 是原子的这一事实并不能使每个操作都成为原子的。如果您执行两个原子操作,它们作为一个整体不会是原子的,因此if (waiting == MAX) 正在读取一个值,然后做出决定,然后waiting++ 正在更新该值。在此处使用比较和交换可能是在一个原子访问中完成这两项操作的解决方案:

atomic_uint waiting;
unsigned expected = waiting.load();
do {
  desired = expected + 1;
} while (desired < MAX_WAITING &&
         waiting.compare_exchange_weak(expected, desired));

这里,compare_exchange_weak 检查内存中的值是否与desired 匹配,如果是,则返回true。否则它将返回false 并使用最后一个值更新expected。在这种情况下,您只需要重新检查您要存储的值是否仍符合边界 (

【讨论】:

  • 非常感谢您的回复!关于: 1. 会有数据竞赛吗?我已经将 num_waiting 设置为 atomic 所以据我了解,这个值不应该有任何数据竞争,不是吗? 2. 谢谢。我一定是弄错了文档,谢谢你的接机,
  • 原子操作保证任何两个线程在一个原子上做一个单一的操作都保证得到一个确定的结果。例如,如果两个线程执行numWaiting++,那么最终内存中的值将增加 2。但是,您正在执行 2 个不同的原子操作,根据定义,这不是原子操作:首先您正在加载值以计算条件,那么你正在执行增量。
  • 如果在mem_shop.num_waiting++ 之前加上std::this_thread::sleep_for(std::chrono::seconds(2)) 会更容易理解。你甚至可以使用调试器来查看它的效果。
猜你喜欢
  • 2011-09-30
  • 1970-01-01
  • 2011-07-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多