Поток C++11: несколько потоков, ожидающих переменной условия

в настоящее время я работаю над проблемой, которая имитирует расширенную модель производитель-работник. В этой задаче есть 3 рабочих и 3 инструмента, а для работы рабочим нужны 2 инструмента (и материалы, но они не имеют значения). Если в хранилище есть >=2 инструмента, рабочий возьмет 2. В противном случае они будут ждать переменной условия, которая будет сигнализироваться, когда есть >=2.

это нормально с 2 работниками: один будет работать, а затем вернет инструменты в хранилище, а другой ожидающий работник проснется и возьмет 2 инструмента. Проблема в том, что с 3 рабочими всегда будет один голодающий, чтобы получить инструменты.

после некоторого тестирования я заметил, что потоки, ожидающие переменной условия, структурированы в форме стека. В любом случае, можно ли сделать его в очереди? (1 ждет, 2 ждет и 3 ждет. когда 1 просыпается и хочет сделать другого, он должен ждать позади 2 и 3.)

вот один пример вывода. Код слишком длинный, поэтому я опубликую если это действительно необходимо. Есть 3 рабочих потока и 1 инструмент мьютекс. Тот, кто голодает, отличается от всех остальных.

1 Tools taken. Remaining: 1
2 Waiting on tools...
3 Waiting on tools...
1 Operator Product made. Tools returned. Tools now:3
3 Tools taken. Remaining: 1
1 Waiting on tools...
3 Materials returned for switch.
3 Operator Product made. Tools returned. Tools now:3
1 Tools taken. Remaining: 1
3 Waiting on tools...
1 Materials returned for switch.
1 Operator Product made. Tools returned. Tools now:3
3 Tools taken. Remaining: 1
1 Waiting on tools...
3 Materials returned for switch.
3 Operator Product made. Tools returned. Tools now:3
1 Tools taken. Remaining: 1
3 Waiting on tools...
1 Materials returned for switch.
1 Operator Product made. Tools returned. Tools now:3
3 Tools taken. Remaining: 1
1 Waiting on tools...
3 Materials returned for switch.
3 Operator Product made. Tools returned. Tools now:3
1 Tools taken. Remaining: 1
3 Waiting on tools...
1 Materials returned for switch.
...

(Как видите 2 не получает средства...)

обновление: 2013/07/05 Я добавил код.

int tools = 3; //global
string last; //current last product on output buffer
mutex toolsMutex;
mutex matSearchMutex;

int main(){
//Initializing Producers
    Producer prod1(1);
    Producer prod2(2);
        Producer prod3(3);



    thread p1(processor,1);
    thread p2(processor,2);
    thread p3(processor,3);

    p1.detach();
    p2.detach();
    p3.detach();

    while(true){//forever running

    }

    return 0;
}

процессор:

  //Processor method
void processor(int i){
    srand(time(NULL)); 

    while (true){ //forever running


    bool hasTools = false;
    bool productMade = false;
    while (productMade == false){ //while product has yet to be made.
        //choose what to make...



        if (hasTools == false){
            thread matT(getMaterials,whatToMake);
            thread toolT(getTools,i);
            toolT.join();           
            matT.join();
            hasTools = true;
        }
        else{ //tools acquired but no materials
            thread matT(getMaterials,whatToMake);
            matT.join();
        }

        if (recordedLast.compare(last) != 0){

            //return materials and acquire new ones the next run

            continue;
        }
        else {
            makeProduct(whatToMake);
            unique_lock<mutex> locker(toolMutex); 
            tools = tools + 2;
            cout << i << " Operator Product made. Tools returned. Tools now:" << tools << endl;
            productMade = true;
            if (tools >=2)  toolsCV.notify_one();
        }

    //done processing

    }


}   

}

makeProducts:

void makeProduct(int i){
    unique_lock<mutex> mainMatLock(matSearchMutex); 
    // make product according to i
    this_thread::sleep_for(chrono::milliseconds(rand() % 1000 + 10));   
}

getTools:

void getTools(int i){
    unique_lock<mutex> locker(toolMutex); 
    if (tools <2){
        cout << i << " Waiting on tools..." << endl;
        toolsCV.wait(locker);}
    tools = tools - 2;//tools acquired
    cout << i <<" Tools taken. Remaining: " << tools << endl;

}

спасибо тем, кто ответил. Я попытаюсь реализовать очередь ожидания сегодня вечером, используя несколько переменных условий.

(П. С. есть лучший способ, чтобы сделать код форматирования здесь, на переполнение стека? Кроме четырех мест...

4 ответов


std::condition_variable не указывает, какой ожидающий поток пробуждается при вызове notify_one. Поэтому вы должны написать код, который не заботится о том, какой поток пробуждается. Стандартный шаблон заключается в том, что какой бы поток ни был разбужен, этот поток должен выполнять работу, которая должна быть выполнена.

Если требуется, чтобы потоки были разбужены в определенном порядке, используйте другой механизм. Вы могли бы, например, иметь отдельный std::condition_variable для каждого потока, а затем поместите потоки в очередь, когда они нужны инструменты. Поскольку поток передает инструменты, он может затем сигнализировать переменную условия, соответствующую потоку в передней части очереди. Затем эта нить будет разбужена, а остальные останутся спящими (по модулю ложных пробуждений).


когда несколько потоков ждут на условии, заказ в которой они пробуждаются (notify_all) или один проснулся (notify_one) является неуказанным. Если вам нужен какой-то для заказа необходимо использовать notify_all, и реализовать ее себе. Вы можете держать очередь потоков в ожидании: before ожидание (но после получения мьютекса), нажмите идентификатор потока на конец очереди. В цикле, цикл на " этом потоке на перед очереди и необходимые инструменты". Когда ты ... этот инструменты, удалите идентификатор из передней части очереди и вызовите notify_all снова.


реальная проблема здесь заключается в том, что если у вас есть рабочие потоки и ограниченное количество необходимых ресурсов, вы не должны заботиться о том, какие потоки фактически активированы, вы должны заботиться только о том, чтобы работа была выполнена. Единственная разница здесь в регистрации. Определенное количество потоков - это количество потоков, которые могут выполняться параллельно, ограниченное ресурсами до одного.

Если это не хорошо для вас, то вы должны принять меры самостоятельно, как описано в других ответы.


одним из подходов может быть использование полноценного семафора, который совместно используется между потоками вместо переменной условия. Таким образом, вы можете подождать определенного количества.

#include <mutex>
#include <thread>
#include <condition_variable>

using std::mutex;
using std::condition_variable;

class Semaphore
{
public:
    /**
     * Construct a counting semaphore with an initial value
     * @param cnt The value of the initial semaphore count
     */
    Semaphore(unsigned int cnt);

    /**
     * acquire a semaphore count
     * @param numRes The number of count ressources to acquire
     */
    void acquire(unsigned int numRes = 1);

    /**
     * try to acquire a semaphore count.
     * @param numRes The number of count ressources that the method tries to acquire
     * @return true, if numRes could be aquired
     *         false, otherwise
     */
    bool tryAcquire(unsigned int numRes = 1);

    /**
     * release one semaphore cnt
     * @param numRes The number of count ressources to release
     */
    void release(unsigned int numRes = 1);

private:
    unsigned int cnt;
    mutex mut;
    condition_variable cond;
};

реализация выглядит так:

void Semaphore::acquire(unsigned int numRes)
{
    unique_lock<mutex> lock(mut);
    while (cnt < numRes)
    {
        cond.wait(lock);
    }

    cnt-=numRes;
}

bool Semaphore::tryAcquire(unsigned int numRes)
{
    unique_lock<mutex> lock(mut);
    if (cnt>=numRes)
    {
        cnt -= numRes;
        return true;
    }
    return false;
}

void Semaphore::release(unsigned int numRes)
{
    {
        unique_lock<mutex> lock(mut);
        cnt += numRes;
    }
    // notify <numRes> waiting entities
    for (unsigned int i = 0; i<numRes; ++i)
    {
        cond.notify_one(); 
    }
}