如何允许多个线程访问C ++中符合条件的关键部分

时间:2019-05-09 11:14:51

标签: c++ multithreading concurrency

![铁路]:http://www.cs.tut.fi/~rinn/htyo/design/pics/railroad.png

我正在尝试创建一个模拟上面铁路的火车的仿真软件。并发问题源于以下约束条件:

*每个轨道段一次是单向的:当一列火车开始向一个方向行驶时,再也没有其他火车朝相反的方向行驶(注意:这允许多列火车沿同一方向行驶。)

*当火车在某个路段上开始行驶时,应该跑到下一个路段/路口(换句话说:在路段中间不要掉头)

*火车不必有特定的时间表(它们的整体运动在整个轨道网络中可以是随机的)。

*在任何数量的列车可以挂在其上(等待)的意义上,轨道交换/切换位置可以是“魔术”,所有列车可以彼此通向其中的任何方向。

我的计划是创建2个变量,一个变量包含当前轨道上火车的当前方向,另一个变量将计算轨道上火车的数量。想法是首先检查是否允许火车进入轨道(换句话说,轨道上的方向==离开火车的方向,或者如果轨道为空),然后增加轨道上的火车数量。当轨道上的最后一列火车到达下一个路口时,方向变量将设置为0,以允许任何火车进入该轨道。

据我所知,我必须创建自己的某种锁定机制才能实现此目的,但是我对实现的细节一无所知。条件变量似乎是有用的,但是我阅读的所有建议都使用了互斥量,这不适用于我们的情况。也许有条件变量的人?

如果这些方法有帮助,那么这里是到目前为止的实现: railroad.cc

#include <chrono>
#include <thread>
#include <iostream>
#include <mutex>
#include <atomic>
#include "railroad.hh"


std::atomic_int lock = 0;
std::mutex cout_mutex;
Railroad::Railroad(std::vector<Track> tracks, std::vector<int> junctions, std::vector<Train> trains):
    tracks(tracks), junctions(junctions), trains(trains)
{

}

bool Railroad::moveTrain(int id)
{
    for(int i = 0; i < 10; i++){
        std::this_thread::sleep_for(std::chrono::milliseconds(std::rand()%300));
        bool finished = false;
        int target_id = rand()%6;
        int start;
        for(auto train_it = trains.begin(); train_it != trains.end(); train_it++){
            if(finished){
                break;
            }
            if(train_it->getId() == id){
                start = train_it->getLocation();
                train_it->setMoving(true);
                for(auto track_it = tracks.begin(); track_it != tracks.end(); track_it++){
                    if(track_it->id == target_id){
                        finished = true;
                        if(start == track_it->point2){
                            track_it->in_use == true;
                            cout_mutex.lock();
                            std::cout << "Train " << id
                                      << " started moving on track "
                                      << target_id << std::endl;
                            cout_mutex.unlock();
                            std::this_thread::sleep_for(std::chrono::milliseconds(std::rand()%1000));
                            train_it->setLocation(track_it->point1);
                            train_it->setMoving(false);
                            cout_mutex.lock();
                            std::cout<< "Train " << id << " has arrived from "
                                     << start << " to " << track_it->point1
                                     <<std::endl;
                            cout_mutex.unlock();
                            break;
                        }
                        else if(start == track_it->point1){
                            track_it->in_use == true;
                            cout_mutex.lock();
                            std::cout << "Train " << id << " started moving on track "
                                      << target_id << std::endl;
                            cout_mutex.unlock();
                            std::this_thread::sleep_for(std::chrono::milliseconds(std::rand()%1000));
                            train_it->setLocation(track_it->point2);
                            train_it->setMoving(false);
                            cout_mutex.lock();
                            std::cout<< "Train " << id << " has arrived from " << track_it->point1 << " to " << track_it->point2 <<std::endl;
                            cout_mutex.unlock();
                            break;
                        }
                        else{
                            cout_mutex.lock();
                            std::cout<< "Train " << id << " cannot access "<<track_it->id << std::endl;
                            cout_mutex.unlock();
                            break;
                        }
                    }
                }
            }
        }
    }
}


main.cpp

#include <iostream>
#include <memory>
#include <random>
#include <thread>
#include "railroad.hh"


using namespace std;

int main()
{
    std::vector<Track> tracks;
    tracks.push_back({0,0,2, false});
    tracks.push_back({1, 2, 3, false});
    tracks.push_back({2, 3, 0, false});
    tracks.push_back({3, 0, 1, false});
    tracks.push_back({4, 1, 2, false});
    tracks.push_back({5, 1, 3, false});

    std::vector<int> junctions;
    std::vector<Train> trains;

    trains.push_back({0,0,false});
    trains.push_back({1,1,false});
    trains.push_back({2,2,false});
    junctions.push_back(0);
    junctions.push_back(1);
    junctions.push_back(2);
    junctions.push_back(3);
    Railroad* railroad = new Railroad(tracks, junctions, trains);

    std::thread t1(&Railroad::moveTrain,railroad,0);
    std::thread t2(&Railroad::moveTrain,railroad,1);
    std::thread t3(&Railroad::moveTrain,railroad,2);


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

1 个答案:

答案 0 :(得分:1)

基本上,您想要的是一列试图输入特定轨道以等待直到轨道上没有火车或者所有轨道的方向与您的火车想要驶入的方向相同的火车。解决此问题的一种方法是使用std::condition_variable。条件变量允许您阻塞线程,直到特定条件变为真(因此命名)为止。对于每个轨道,您都有一个计数器来计算轨道上的火车数量。您可以使用单独的变量,也可以仅使用计数器的符号来跟踪轨道上的当前方向(例如,逆时针计数为正,顺时针计数为负)。每当火车要进入轨道时,它都会检查计数器是否为零或方向是否与要进入的方向相同。如果不是这种情况,它将等待此条件变为真。由于可以同时修改计数器,因此必须使用互斥锁来锁定对计数器的访问。您获取互斥锁并检查计数器。如果计数器使您可以进入音轨,请更新计数器,解锁互斥锁,然后继续。如果计数器使您当前无法进入轨道,请在条件变量上调用wait。调用wait时,您必须移交当前持有的锁。对条件变量的wait操作将使您的线程自动进入睡眠状态并释放互斥锁,以便其他线程同时可以对计数器进行处理(否则,该条件永远不会变为真)。每当火车离开轨道时,它将获取锁,更新计数器,如果是最后一列火车,则通知条件变量。此通知操作将唤醒当前等待条件变量的所有线程。在这些线程中的每个线程中,被阻塞的线程的等待调用将重新获取互斥锁并返回。因此,线程一个接一个地再次检查它们正在等待的条件现在是否为真。如果是,则他们可以继续进行;否则,他们会继续等待。

最好将所有这些封装在一个类中,例如:

#include <mutex>
#include <condition_variable>

enum class Direction
{
    CCW = 1,
    CW = -1
};

class Track
{
    int counter = 0;

    std::mutex m;
    std::condition_variable track_free;

    static int sign(Direction direction)
    {
        return static_cast<int>(direction);
    }

    bool isFree(Direction direction) const
    {
        return (sign(direction) > 0 && counter >= 0) || (sign(direction) < 0 && counter <= 0);
    }

public:
    void enter(Direction direction)
    {
        std::unique_lock lock(m);

        while (!isFree(direction))
            track_free.wait(lock);

        counter += sign(direction);
    }

private:
    bool release(Direction direction)
    {
        std::lock_guard lock(m);
        counter -= sign(direction);
        return counter == 0;
    }

public:
    void exit(Direction direction)
    {
        if (release(direction))
            track_free.notify_all();
    }
};

然后您的火车就拨打,例如

track.enter(Direction::CW);

输入曲目,然后

track.exit(Direction::CW);

他们要离开的时候...