2017-05-08 67 views
2

我有一个程序将10个线程放到一个向量中,每个向量应该在完成前打印出一个字符5次('A'代表第一个线程,'B'为第二个,等等)。我可以让它们一次运行(使用detach())或让它们一次运行一个(使用join())。现在我想用一个Mutex来限制每次允许打印的线程数为2.我已经能够声明互斥锁并将锁定到位,但我不确定如何应用这样的限制。任何人有任何想法如何进行?使用互斥锁限制每次运行的线程数为2

deque<int> q ; 
mutex print_mutex ; 
mutex queue_mutex ; 
condition_variable queue_cond ; 

void begin(int num) { 
    unique_lock<mutex> ul {queue_mutex}; 
    q.emplace_back(num); 
    queue_cond.wait(ul,[num]{ 
     return q.front() == num; }); 
    q.pop_front(); 
    cout << num << " leaves begin " << endl ; 
} 

void end (int num) { 
    lock_guard<mutex>lg{queue_mutex}; 
    queue_cond.notify_all(); 
    cout << num << " has ended " << endl ; 
} 

void run(int num, char ch) { 
    begin(num); 
    for (int i = 0; i < 5; ++i) { 
     { 
      lock_guard<mutex> lg { print_mutex }; 
      cout << ch << endl << flush ; 
     } 
     sleep_for(milliseconds(250)); 
    } 
    end(num); 
} 

int main() { 
    vector<thread>threads {}; 
    for (int i = 0; i < 10; ++i) { 
     threads.push_back(thread{run,i,static_cast<char>(65+i)}); 
     threads.at(i).join(); 
    } 
} 
+1

你需要一个整数来计算正在运行的线程的数量。 –

+1

使用'detach()'和'join()'来控制排序是错误的。 'main'中的'for'循环只是串行化线程;这是毫无意义的。使用一个循环来创建全部**的线程,然后是一个单独的循环,用于连接全部**的线程。然后找出你想如何交互的线程。不要使用'detach()';这绝对不是你需要的。 –

+0

@PeteBecker非常棒,那是我的问题!优秀的解释。我试图添加一个更好的锁定条件,但没有任何更改,直到我将连接分隔到另一个循环。如果你想继续,并作为答案张贴,我会很乐意接受它。 – gmooney8

回答

3

您已经为全局deque<int> q设置了一个FIFO。所以让我们使用它。

目前,您正试图限制执行,直到当前线程位于前面。虽然有一个错误,因为begin会立即从双端队列中弹出该线程。当您致电end时,最好删除该值。下面是变化,第一:

void end(int num) 
{ 
    { 
     lock_guard<mutex>lg{queue_mutex}; 
     cout << num << " has ended " << endl ; 
     q.erase(find(q.begin(), q.end(), num)); 
    } 
    queue_cond.notify_all(); 
} 

这使用std::find<algorithm>删除特定值。您可以使用pop_front,但我们即将更改该逻辑,以便更通用。另请注意,通知时不需要锁定条件变量。

因此,将begin中的逻辑扩展到前两位并没有多大的延伸。在这里:

void begin(int num) 
{ 
    unique_lock<mutex> ul {queue_mutex}; 
    q.emplace_back(num); 
    queue_cond.wait(ul,[num]{ 
     auto end = q.begin() + std::min(2, static_cast<int>(q.size())); 
     return find(q.begin(), end, num) != end; 
     }); 
    cout << num << " leaves begin " << endl ; 
} 

可以更改2到任何你想要的,最多允许多个线程通过。在某些时候,您可能会放弃这种方法,并使用一个简单的方法,如单个计数器变量,然后依靠线程调度器来管理哪个线程被唤醒,而不是强制它们进入FIFO。这样你就可以切换到使用notify_one来唤醒单线程并减少开销开销。

无论如何,最后要做的就是从线程生成循环中删除join。并发现在由beginend管理。所以你应该这样做:

for (int i = 0; i < 10; ++i) { 
    threads.push_back(thread{run, i, 'A'+i}); 
} 
for (auto & t : threads) t.join(); 
+0

很好的解释,稻田!我最终找到了一个不同的解决方案(使用你提到的计数器),但你的解释更好地理解它。谢谢! – gmooney8

+0

我忘记提到的一件事是你的开始/结束代码不是异常安全的。如果你的线程因异常而终止,那么'end'永远不会被调用。你可能会考虑在对象中包装开始/结束调用,以便RAII处理它。 – paddy