C ++多线程 - 条件变量以错误的方式提供数据

Posted

技术标签:

【中文标题】C ++多线程 - 条件变量以错误的方式提供数据【英文标题】:C++ multithreading - condition variable supplying data in a wrong way 【发布时间】:2020-05-23 17:53:15 【问题描述】:

我有这个多线程项目,我应该在其中创建酒店的模拟。

我有这个接待员结构,他们的工作是不断寻找是否有空闲房间(在check_free_rooms 方法中完成,accommodate_guests 是他的线程方法)。如果他找到number_to_check_in,则将is_a_room_ready 设置为true,并通知等待房间的访客线程之一。

每个客人都有一个引用接待员的字段(酒店里只有一个),客人正在等待接待员的条件变量receptionist.cv 收到通知,receptionist.is_a_room_ready 的条件变为真。那么,如果我理解正确的话,一个随机的客人应该得到一个房间,其他的应该耐心等待接待员的另一个通知。

#include <iostream>
#include <vector>
#include <thread>
#include <mutex>
#include <chrono>
#include <algorithm>
#include <experimental/random>
#include <atomic>
#include <ncurses.h>
#include <condition_variable>
#include <memory>

std::mutex mx_writing;

struct Guest;

struct Room

    Room() 
    int id;
    int guest_id;
    std::atomic<bool> is_ready_for_guesttrue;

    void guest_arrives(int guest_id)
    
        this->guest_id = guest_id;
        this->is_ready_for_guest = false;
    
    void guest_leaves(int guest_id)
    
        this->guest_id = -1;
    
;

struct Receptionist

    Receptionist(std::vector<Room> &rooms) : rooms(rooms) 
    std::vector<Room> &rooms;

    std::mutex mx;
    std::condition_variable cv;
    std::atomic<bool> is_a_room_readyfalse;
    int number_to_check_in = 0;

    void check_free_rooms()
    
        std::unique_lock<std::mutex> lock_receptionist(mx);
        do
        
            this->number_to_check_in = std::experimental::randint(0, (int)rooms.size() - 1); //find an empty room
         while (!rooms[this->number_to_check_in].is_ready_for_guest);
        is_a_room_ready = true;
        cv.notify_one();
    

    void accommodate_guests()
    
        while (true)
        
            check_free_rooms();
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        
    
;

struct Guest

    Guest(int id, Receptionist &receptionist, Coffee_machine &coffee_machine,
          Swimming_pool &swimming_pool) : id(id), receptionist(receptionist),
                                          coffee_machine(coffee_machine), swimming_pool(swimming_pool) 
    int id;
    int room_id;

    Receptionist &receptionist;
    Coffee_machine &coffee_machine;
    Swimming_pool &swimming_pool;

    void check_in()
    
        std::unique_lock<std::mutex> lock_receptionist(receptionist.mx);
        while (!receptionist.is_a_room_ready)
        
            receptionist.cv.wait(lock_receptionist);
        
        receptionist.is_a_room_ready = false;
        this->room_id = receptionist.number_to_check_in;           //assign room to guest
        receptionist.rooms[this->room_id].guest_arrives(this->id); //assign guest to room && room becomes occupied
        
            std::lock_guard<std::mutex> writing_lock(mx_writing);
            std::cout << "Guest " << this->id << " accomodated in room " << this->room_id << std::endl;
        
        std::this_thread::sleep_for(std::chrono::milliseconds(20));
    

    void have_holiday()
    
        check_in();
        std::this_thread::sleep_for(std::chrono::milliseconds(std::experimental::randint(500, 700)));
    
;

int main()

    std::vector<Room> rooms(10);
    for (int i = 0; i < 10; i++)
    
        rooms[i].id = i;
    

    Receptionist receptionist(rooms);

    std::vector<Guest> guests;
    for (int i = 0; i < 15; i++)
    
        guests.emplace_back(Guest(i, receptionist, coffee_machine, swimming_pool));
    

    std::vector<std::thread> threadList;

    threadList.emplace_back(std::thread(&Receptionist::accommodate_guests, std::ref(receptionist)));
    for (Guest guest : guests)
    
        threadList.emplace_back(std::thread(&Guest::have_holiday, std::ref(guest)));
    

    for (std::thread &t : threadList)
    
        t.join();
    

    return 0;


但是,客人入住后出现在终端机上的线路与我想象的不太一样。客人 ID 的范围从 0 到 14,房间 ID 的范围从 0 到 9,但只有房间在输出中似乎或多或少没问题。我不知道为什么来宾 id 是一个随机的大整数,而不是我在对象构造时分配的那些。

我对多线程和条件变量不是很有经验,而且几乎不知道如何解决这个问题,因为我想了很多,却一无所获。我非常感谢任何帮助。

一些示例输出:

Guest 5 accommodated in room 32657
Guest -624431120 accommodated in room 7
Guest -624431120 accommodated in room 9
Guest -624431120 accommodated in room 8
Guest -624431120 accommodated in room 5
Guest -624431120 accommodated in room 4
Guest -624431120 accommodated in room 3
Guest -624431120 accommodated in room 2
Guest -624431120 accommodated in room 0
Guest -624431120 accommodated in room 6
^C

Guest 4 accommodated in room 32539
Guest -497561616 accommodated in room 1
Guest -497561616 accommodated in room 9
Guest -497561616 accommodated in room 5
Guest -497561616 accommodated in room 8
Guest -497561616 accommodated in room 6
Guest -497561616 accommodated in room 7
Guest -497561616 accommodated in room 3
Guest -497561616 accommodated in room 4
Guest -497561616 accommodated in room 0
^C

Guest 4 accommodated in room 32746
Guest -1510756368 accommodated in room 8
Guest -1510756368 accommodated in room 1
Guest -1510756368 accommodated in room 7
Guest -1510756368 accommodated in room 4
Guest -1510756368 accommodated in room 2
Guest -1510756368 accommodated in room 5
Guest -1510756368 accommodated in room 9
Guest -1510756368 accommodated in room 6
Guest -1510756368 accommodated in room 3
^C

【问题讨论】:

mx_writing 定义在哪里? for (Guest guest : guests)你在这里复制客人,并参考它? @KorelK 在全球范围内,我编辑了问题 是的,试试for (Guest &amp;guest : guests) 看看会发生什么。 &amp; 表示guest 是对guests 中每个元素的引用。这通常是你想要的,所以更喜欢这种语法。实际上,当您根本不想更改元素时,请执行for (Guest const &amp;guest : guests) 【参考方案1】:

替换:

for (Guest guest : guests) 
    threadList.emplace_back(std::thread(&Guest::have_holiday, std::ref(guest)));

与:

for (Guest &guest : guests) 
    threadList.emplace_back(std::thread(&Guest::have_holiday, std::ref(guest)));

在您的代码中,您将在每次迭代中创建一个 guest 副本,而不是使用现有的。 注意Guest guest [copy] vs Guest &amp;guest [reference].Read about references in C++.

另一个修复:

for (int i = 0; i < 15; i++) 
    guests.emplace_back(Guest(i, receptionist, coffee_machine, swimming_pool));

在本节中,您将在每次迭代中创建两次来宾对象。在emplace_back 中,您可以只为构造函数传递参数而无需创建副本:

for (int i = 0; i < 15; i++) 
    guests.emplace_back(i, receptionist, coffee_machine, swimming_pool);

【讨论】:

你能解释一下为什么第二个 sn-p 没有复制吗? 好多了,谢谢。请注意,另一个修复并不是真正的“修复”,而可能是“改进”。 非常感谢您的解释(也由@cigien 提供)和其他改进建议。 @Theta 没问题。关于改进建议,请注意它也适用于threadList.emplace_back(&amp;Receptionist::accommodate_guests, std::ref(receptionist));threadList.emplace_back(&amp;Guest::have_holiday, std::ref(guest));

以上是关于C ++多线程 - 条件变量以错误的方式提供数据的主要内容,如果未能解决你的问题,请参考以下文章

基于条件变量的消息队列

Linux C 多线程编程之互斥锁与条件变量实例详解

C++11多线程编程——生产消费者模型之条件变量

并发编程多线程程序同步策略

[多线程]C++11多线程-条件变量(std::condition_variable)

Linux多线程编程与同步实例(基于条件变量)