C++:带有"std::lock_guard"的互斥锁是否足以同步两个"std::thre

C++: Is a mutex with `std::lock_guard` enough to synchronize two `std::thread`s?

本文关键字:quot std 同步 两个 thre lock 带有 guard C++ 是否      更新时间:2023-10-16

我的问题基于下面的C++代码示例

#include <chrono>
#include <thread>
#include <mutex>
#include <iostream>
class ClassUtility
{
public:
ClassUtility() {}
~ClassUtility() {}
void do_something() {
std::cout << "do something called" << std::endl;
using namespace std::chrono_literals;
std::this_thread::sleep_for(1s);
}
};

int main (int argc, const char* argv[]) {
ClassUtility g_common_object;
std::mutex  g_mutex;
std::thread worker_thread_1([&](){
std::cout << "worker_thread_1 started" << std::endl;
for (;;) {
std::lock_guard<std::mutex> lock(g_mutex);
std::cout << "worker_thread_1 looping" << std::endl;
g_common_object.do_something();
}
});

std::thread worker_thread_2([&](){
std::cout << "worker_thread_2 started" << std::endl;
for (;;) {
std::lock_guard<std::mutex> lock(g_mutex);
std::cout << "worker_thread_2 looping" << std::endl;
g_common_object.do_something();
}
});

worker_thread_1.join();
worker_thread_2.join();
return 0;
}

这更像是一个让我理解清楚的问题,而不是获得所需的std::condition_variable的示例用法。

我有 2 个C++std::threadmain方法启动。它是osx上的控制台应用程序。所以使用叮当声编译它。两个线程都使用一个公共对象ClassUtility调用方法做一些繁重的任务。对于此示例代码来解释这种情况,两个线程都运行无限循环并仅在以下情况下关闭 该应用程序关闭,即当我在控制台上按ctrl+c时。

想知道:

如果我在std::mutex上使用std::lock_guard来同步或保护对ClassUtilitycommon_obejct的调用是否正确。不知何故,我似乎 陷入这种">只是互斥体方法"的麻烦。如果我使用互斥锁锁定保护循环,则不会启动任何线程。此外,我有时会出现段错误。这是因为它们是拉姆达吗? 分配给每个线程 ?

在 2 个线程或 lambda 之间使用std::condition_variable来发出信号并同步它们是否更好?如果是,那么如何使用std::condition_variable在拉姆达斯之间?

注意:由于问题只是寻找信息,因此此处提供的代码可能无法编译。只是为了提供一个真实的场景

你的代码是安全的

请记住,lock_guard只是调用.lock()并将调用注入到块的末尾.unlock()。所以

{
std::lock_guard<std::mutex> lock(g_mutex);
std::cout << "worker_thread_1 looping" << std::endl;
g_common_object.do_something();
}

基本等同于:

{ 
g_mutex.lock();
std::cout << "worker_thread_1 looping" << std::endl;
g_common_object.do_something();
g_mutex.unlock();
}

除了:

  1. 即使通过异常离开块,也会调用解锁,并且
  2. 它确保您不会忘记打电话。

您的代码不是并行的

您相互排除每个线程中的所有循环体。两个线程实际上可以并行执行的操作已不复存在。使用线程的要点是当每个线程都可以处理单独的对象集(并且只读取公共对象)时,因此不必锁定它们。

在示例代码中,您实际上应该锁定公共对象上的工作;std::cout本身是线程安全的。所以:

{ 
std::cout << "worker_thread_1 looping" << std::endl;
{
std::lock_guard<std::mutex> lock(g_mutex);
g_common_object.do_something();
// unlocks here, because lock_guard injects unlock at the end of innermost scope.
}
}

我想你尝试编写的实际代码确实有一些实际并行做的事情;只是要记住一件事。

不需要条件变量

条件变量适用于您需要一个线程等待另一个线程执行某些特定操作的情况。在这里,您只是确保两个线程不会同时修改对象,并且为此mutex是足够且合适的。

你的代码永远不会终止,除了我挑不出毛病。

正如其他人指出的那样,它几乎没有提供并行性的机会,因为当互斥锁锁定到休眠线程时会发生长时间的休眠。

这是一个简单的版本,它通过在循环上放置任意有限限制来终止。

也许你不明白join()是做什么的? 它是当前线程(正在执行join()),直到连接的线程结束。但如果它没有结束,当前线程也不会结束。

#include <chrono>
#include <thread>
#include <mutex>
#include <iostream>
class ClassUtility
{
public:
ClassUtility() {}
~ClassUtility() {}
void do_something() {
std::cout << "do something called" << std::endl;
using namespace std::chrono_literals;
std::this_thread::sleep_for(1s);
}
};

int main (int argc, const char* argv[]) {
ClassUtility g_common_object;
std::mutex  g_mutex;
std::thread worker_thread_1([&](){
std::cout << "worker_thread_1 started" << std::endl;
for (int i=0;i<10;++i) {
std::lock_guard<std::mutex> lock(g_mutex);
std::cout << "worker_thread_1 looping " << i << std::endl;
g_common_object.do_something();
}
});

std::thread worker_thread_2([&](){
std::cout << "worker_thread_2 started" << std::endl;
for (int i=0;i<10;++i) {
std::lock_guard<std::mutex> lock(g_mutex);
std::cout << "worker_thread_2 looping " << i << std::endl;
g_common_object.do_something();
}
});

worker_thread_1.join();
worker_thread_2.join();
return 0;
}