Поток 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();
}
}