我正在尝试学习线程,互斥等的基础知识。遵循here的文档和示例。在下面的代码中,我得到预期的输出。问题:
cv.notify_all();
吗? #include<mutex>
#include<iostream>
#include<thread>
#include<condition_variable>
std::mutex mtx;
std::condition_variable cv;
int currentlyRequired = 1;
void work(int id){
std::unique_lock<std::mutex> lock(mtx); // Line 11
while(currentlyRequired != id){
cv.wait(lock);
}
std::cout<<"Thread # "<<id<<"\n";
currentlyRequired++;
cv.notify_all();
}
int main(){
std::thread threads[10];
for(int i=0; i<10; i++){
threads[i] = std::thread(work, i+1);
}
for(int i=0; i<10; i++){
threads[i].join();
}
return 0;
}
/* Current Output:
Thread # 1
Thread # 2
Thread # 3
Thread # 4
Thread # 5
Thread # 6
Thread # 7
Thread # 8
Thread # 9
Thread # 10
Program ended with exit code: 0
*/
最佳答案
首先,我建议您在这里阅读文档:cppreference.com
对于将条件变量用于线程间等待和通知所需要遵循的关键点,您要谨慎得多。
我认为您的代码可以衡量。
您的代码将按照您的想法在第11行获得锁。 unique_lock
的其他构造函数将采用先前已锁定(由当前线程锁定)或未锁定(由当前线程锁定)的互斥锁,并在请求时将其锁定(此处为lock.lock();
)。
你所拥有的是正确的。您检查持有锁的相关数据。
等待,然后将其解锁(通过unique_lock
)并等待通知。
收到通知后,它将停止等待,再次将其锁定并循环检查条件。
最终,条件为真,并且每个线程(仍保持锁定状态)继续进行其“工作”。
“等待”方面看起来是正确的。 “通知”面也看起来是正确的。
必须修改包含互斥量的条件数据,以确保检查数据并进入条件变量管理的等待状态的正确同步。
您正确notify_all()
。即使在逻辑上(在此示例中)仅需要唤醒一个线程,也无法选择将其作为notify_one()
的目标。
因此,所有线程都被唤醒(如果挂起),请检查它们的状态,并且恰好其中之一可以识别出该线程并运行。
常识(和我的经验)是notify_all()
不持有锁会更好(并且更有效),因为等待的线程会醒来阻塞(在锁上)。但是我被告知某些平台在锁定状态下可以更好地进行通知。欢迎来到平台依赖的世界...
因此,就实现条件变量而言,我认为这是有效的,而且几乎是教科书。
也很高兴看到join()
。我有一个关于编码器未加入线程的错误。
这是您在小规模和高负载情况下无法使用的应用程序之一,但是当应用程序扩展并遇到高负载时,可能会引发问题和困惑。
我所做的工作没有并行性。
您已经实现了一条菊花链。这样做的目的是确保只有一个线程同时“执行工作”,并且它们必须严格执行。
为了利用并发性,我们希望最大化并行度-做“工作”的并行运行线程的数量(即,不是线程间通信的内务处理)和您的代码按字面意思(因为您做对了!)确保确保存在并发性永远不会有一个以上的线程在运行,并且代码由于保持工作而保证比单线程应用程序要慢一些(这将是for循环!)。
因此,它在程序正确性方面获得了最高的评价,但在有用方面没有任何评价!
在我看来,cplusplus.con和cppreference.com上的两个示例都稍好一点。
最好的介绍性例子是某种生产者消费者模型。
这更接近于实际有用的模式。
尝试简单的操作,例如一个生产者线程通过整数进行累加,然后多个消费者线程将它们平方并输出。
关键点在于,如果您做对了,则方块通常不会按顺序出现。
这些示例就像通过阶乘教学递归一样。当然,它是递归的,但是递归是计算阶乘的一种糟糕方法!
确保您的多线程(其他示例)有效,但是它们完全是并行的,无济于事!
请不要以为批评。作为“ dentry 切割”运动,您所拥有的是一流的。下一个任务是做一些有用的并发操作!
问题是我们不需要条件变量!根据定义,它们使线程彼此等待,从而减少了并行性。我们需要他们,他们会做自己的工作。但是,减少相互等待量(在锁定或条件下)的设计通常更好,因为这里的敌人正在等待,阻塞或(最差)旋转,而不是暂停的等待/阻塞。
这是为您的任务设计的更好的方案。更好,因为它完全避免了条件变量!!
#include<mutex>
#include<iostream>
#include<thread>
std::mutex mtx;
int currentlyRequired = 1;
void work(int id){
std::lock_guard<decltype(mtx)> guard(mtx);
const auto old{currentlyRequired++};
std::cout<<"Thread # "<<id<<" "<< old << "-> " <<currentlyRequired<< "\n";
}
int main(){
std::thread threads[10];
for(int i=0; i<10; i++){
threads[i] = std::thread(work, i+1);
}
for(int i=0; i<10; i++){
threads[i].join();
}
std::cout << "Final result: " << currentlyRequired << std::endl;
return 0;
}
标本输出:Thread # 7 1-> 2
Thread # 8 2-> 3
Thread # 9 3-> 4
Thread # 10 4-> 5
Thread # 6 5-> 6
Thread # 5 6-> 7
Thread # 4 7-> 8
Thread # 3 8-> 9
Thread # 2 9-> 10
Thread # 1 10-> 11
Final result: 11
哪个线程执行哪个增量将有所不同。但是最终结果总是11。仍然没有好处,因为仍然没有并行性,因为工作都在一个锁下完成。这就是为什么我们需要更有趣的任务。
玩得开心。
关于c++ - 有没有比下面更好的方法来使用C++并发?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64464193/