C++中的互斥锁是否必须绑定到某个对象或变量?

do mutex's in C++ have to be tied to some object or variable?

本文关键字:对象 变量 绑定 是否 C++      更新时间:2023-10-16

我对线程有点陌生,我试图了解它在 C++11 中的工作原理。我班上的教授给了我们这个示例代码来演示互斥锁的使用:

#include <list> 
#include <mutex> 
#include <algorithm>
std::list<int> some_list; // A data structure accessed by multiple threads
std::mutex some_mutex; // This lock will prevent concurrent access to the shared data structure
void
add_to_list(int new_value) {
    std::lock_guard<std::mutex> guard(some_mutex); // Since I am going to access the shared data struct, acquire the lock
    some_list.push_back(new_value); // Now it is safe to use some_list. RAII automatically releases lock at end of function }
}
bool
list_contains(int value_to_find) {
    std::lock_guard<std::mutex> guard(some_mutex); // Must get lock every time I access some_list return
    std::find (some_list.begin(),some_list.end(),value_to_find) != some_list.end();
}

我认为代码有些不言自明,但我有一些具体的问题。

  1. 是否不需要专门将互斥锁与列表相关联?
  2. 如果不是,这是否意味着每当使用互斥锁时,所有线程都会停止,直到互斥锁被销毁?或者它只是线程的子集;也许是某个线程池中的线程或以其他方式相互关联?
  3. 无论哪种情况,只停止尝试访问数据结构的线程不是更好吗?因为除此之外,我们并不担心数据竞争等。
  4. 最后,互斥锁和锁有什么区别?互斥锁只是 RAII 锁吗?还是 RAII 是通过警卫发生的?
  1. 互斥锁与列表相关联,但这种关联完全是手动的——编译器和运行时库不知道两者是关联的。 关联完全存在于您的文档和头脑中,您负责确保访问列表的任何线程首先锁定/获取互斥锁。

  2. 每当使用互斥锁
  3. 时,锁定/获取互斥锁的线程将停止(该术语实际上是),直到没有其他线程拥有互斥锁。 未使用互斥锁的线程将不受影响。

  4. 您负责确保只有访问列表的线程锁定/获取互斥
  5. 锁,并且您还负责确保访问列表的所有线程锁定/获取互斥锁。 同样,只有这些线程可以阻止等待互斥锁。

  6. 同一对象有许多不同的名称:"互斥锁"、"锁定"或"关键部分"。 守卫使用 RAII 锁定/获取互斥锁。

是否不需要专门将互斥锁与列表相关联?

不,或者至少不是明确,这通常也不可能以声明性的方式进行。您的代码每次需要访问受该互斥锁保护的对象时都必须(尝试)获取适当的互斥锁,并在完成读取或更改其状态后释放它。关于哪个互斥锁保护哪个对象的知识只在程序员的脑海中(当然,在程序的文档中)。

如果不是,这是否意味着每当使用互斥锁时,所有线程都会停止,直到互斥锁被销毁?或者它只是线程的子集;也许是某个线程池中的线程或以其他方式相互关联?

这意味着所有想要对某个对象执行原子序列操作的线程首先必须获取保护该对象的互斥锁,以便与竞争相同资源的其他线程同步访问。互斥锁保证只有一个线程可以拥有它,并且在此线程释放(而不是"销毁")互斥锁之前,所有其他线程将等待获取它。

无论哪种情况,只停止尝试访问数据结构的线程不是更好吗?因为除此之外,我们并不担心数据竞争等。

是的,确实如此。通过强制需要独占访问某个对象的线程(并且仅那些线程)获取保护它的互斥锁,您不会强制其他线程停止。

最后,互斥锁和锁有什么区别?互斥锁只是 RAII 锁吗?还是 RAII 是通过警卫发生的?

互斥锁是客户端可以(尝试)获取的同步对象,用于对该互斥锁

保护的对象执行某些操作,而不必担心其他线程干扰其操作(当然,只要其他线程也遵守在访问对象之前尝试获取互斥锁的协议)。

锁通常被理解为一个 RAII 包装器对象,它封装了互斥锁的锁定,其析构函数在调用时自动解锁互斥锁。因此,当锁超出范围时(因为从函数返回,或者因为抛出异常等),获取的互斥锁会自动释放。

是否不需要专门将互斥锁与列表相关联?

不,没有。在这种情况下,互斥锁只是保护代码块,而不是列表本身。由于some_list.push_back()std::find是在受同一互斥锁保护的代码块中执行的,因此单独的线程不会在受保护的块中一起传播,直到一个线程退出该块。

如果不是,这是否意味着每当使用互斥锁时,所有线程都会停止,直到互斥锁被销毁?或者它只是线程的子集;也许是某个线程池中的线程或以其他方式相互关联?

否 - 所有尝试进入互斥锁保护块的线程都将挂起,直到互斥锁解锁(这可以通过销毁持有互斥锁的对象来实现lock_guard)。

无论哪种情况,只停止尝试访问数据结构的线程不是更好吗?因为除此之外,我们并不担心数据竞争等。

正如我所说,只有尝试访问受保护块的线程才可能被挂起,因此无需阻止所有线程。

最后,互斥锁和锁有什么区别?互斥锁只是 RAII 锁吗?还是 RAII 是通过警卫发生的?

不,互斥锁只是同步原语,在不同的系统上实现不同,但C++为它提供了统一的接口。 互斥锁本身只知道三个操作:locktry_lockunlock,所以它周围有不同的包装器。其中之一,提供 RAII 是 std::lock_guard .

是否不需要专门将互斥锁与列表相关联?

不。 您手动执行此操作。

如果不是,这是否意味着任何时候使用互斥锁时,所有线程 停止直到互斥锁被销毁?或者它只是线程的子集; 可能是某个线程池中的线程,或者与每个线程相关联的线程 其他?

互斥锁 = 互斥。 如果两个线程尝试锁定互斥锁,其中一个线程将阻塞,直到释放互斥锁。 如果您尝试锁定互斥锁,而另一个线程具有锁定,它将阻塞,直到释放为止。

无论哪种情况,只停止线程不是更好吗 正在尝试访问数据结构?因为否则,我们 不担心数据竞赛等。

是的。 只应在并发访问出现问题时锁定保护数据结构的互斥锁。 例如,在修改数据结构时。 这就是为什么您的add_to_list函数仅在some_list.push_back()期间锁定互斥锁(some_mutex)。

最后,互斥锁和锁有什么区别?是互斥锁 只是一个 RAII 锁?

C++ lock_guard是围绕互斥锁的 RAII 包装器。 创建对象时,互斥锁被锁定,当它被销毁(超出范围)时,互斥锁被解锁。