9

我对线程有点陌生,我试图了解它在 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 是通过守卫发生的?
4

4 回答 4

6
  1. 互斥体与列表相关联,但这种关联完全是手动的——编译器和运行时库不知道两者是关联的。该关联完全存在于您的文档和您的头脑中,您负责确保访问列表的任何线程首先锁定/获取互斥锁。

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

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

  4. 同一个对象有许多不同的名称:“mutex”、“lock”或“critical section”。守卫使用 RAII 锁定/获取互斥锁。

于 2013-02-02T19:30:07.197 回答
3

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

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

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

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

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

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

最后,互斥锁和锁有什么区别?互斥锁只是一个 RAII 锁吗?或者 RAII 是通过守卫发生的?

互斥锁是客户端可以(尝试)获取的同步对象,以便对受该互斥锁保护的对象执行某些操作,而不必担心其他线程会干扰其操作(只要其他线程也遵守尝试获取的协议)当然,访问对象之前的互斥锁)。

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

于 2013-02-02T19:35:43.983 回答
3

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

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

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

否 - 所有试图进入由互斥锁保护的块的线程都被挂起,直到互斥锁被解锁(这可能通过破坏lock_guard持有互斥锁的对象来实现)。

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

正如我所说,只有试图访问受保护块的线程可能会被挂起,所以没有必要阻塞所有线程。

最后,互斥锁和锁有什么区别?互斥锁只是一个 RAII 锁吗?或者 RAII 是通过守卫发生的?

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

于 2013-02-02T19:36:30.373 回答
2

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

不,您手动执行此操作。

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

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

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

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

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

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

于 2013-02-02T19:28:55.413 回答