Dev*_*ore 17 c++ multithreading thread-safety
我已经达到了我的项目中的一个要点,它需要在可以写入的资源上的线程之间进行通信,因此同步是必须的.但是,除了基本级别之外,我并不真正理解同步.
考虑此链接中的最后一个示例:http://www.bogotobogo.com/cplusplus/C11/7_C11_Thread_Sharing_Memory.php
#include <iostream>
#include <thread>
#include <list>
#include <algorithm>
#include <mutex>
using namespace std;
// a global variable
std::list<int>myList;
// a global instance of std::mutex to protect global variable
std::mutex myMutex;
void addToList(int max, int interval)
{
// the access to this function is mutually exclusive
std::lock_guard<std::mutex> guard(myMutex);
for (int i = 0; i < max; i++) {
if( (i % interval) == 0) myList.push_back(i);
}
}
void printList()
{
// the access to this function is mutually exclusive
std::lock_guard<std::mutex> guard(myMutex);
for (auto itr = myList.begin(), end_itr = myList.end(); itr != end_itr; ++itr ) {
cout << *itr << ",";
}
}
int main()
{
int max = 100;
std::thread t1(addToList, max, 1);
std::thread t2(addToList, max, 10);
std::thread t3(printList);
t1.join();
t2.join();
t3.join();
return 0;
}
Run Code Online (Sandbox Code Playgroud)
该示例演示了三个线程,两个编写器和一个读取器如何访问公共资源(列表).
使用两个全局函数:一个由两个编写器线程使用,另一个由读取器线程使用.这两个函数都使用lock_guard来锁定相同的资源列表.
现在这就是我无法解决的问题:读者使用与两个编写器线程不同的范围内的锁,但仍锁定相同的资源.这怎么办?我对互斥体的有限理解非常适合编写器函数,你有两个线程使用完全相同的函数.我可以理解,当你即将进入保护区时,检查是正确的,如果其他人已经在里面,你就等了.
但是当范围不同时?这表明存在某种比进程本身更强大的机制,某种运行时环境阻止了"后期"线程的执行.但我认为c ++中没有这样的东西.所以我不知所措.
引擎盖下究竟发生了什么?
min*_*iot 18
我们来看看相关的一行:
std::lock_guard<std::mutex> guard(myMutex);
Run Code Online (Sandbox Code Playgroud)
请注意,lock_guard引用了全局互斥锁myMutex.也就是说,所有三个线程都使用相同的互斥锁.什么lock_guard做基本上是这样的:
myMutex并保持对它的引用.myMutex.互斥锁始终是相同的,它与范围无关.重点lock_guard是让您更容易锁定和解锁互斥锁.例如,如果你手动lock/ unlock,但是你的函数在中间某处抛出异常,它将永远不会到达unlock语句.因此,以手动方式执行此操作,您必须确保互斥锁始终处于解锁状态.另一方面,lock_guard只要退出函数,对象就会自动被破坏 - 无论它是如何退出的.
Vik*_*ram 15
myMutex是全球性的,这是用来保护的myList.guard(myMutex)简单地锁定锁和从块的出口导致其破坏,脱离锁.guard只是一种方便的方式来锁定和解除锁定.
有了这个,mutex不保护任何数据.它只是提供了一种保护数据的方法.它是保护数据的设计模式.因此,如果我编写自己的函数来修改下面的列表,mutex则无法保护它.
void addToListUnsafe(int max, int interval)
{
for (int i = 0; i < max; i++) {
if( (i % interval) == 0) myList.push_back(i);
}
}
Run Code Online (Sandbox Code Playgroud)
锁只有在需要访问数据的所有代码在访问之前锁定锁并且在完成之后才脱离锁之后才有效.这种在每次访问之前和之后接合和解除锁定的设计模式是保护数据的(myList在您的情况下)
现在你会想,为什么要使用mutex,为什么不呢,比如说bool.是的,你可以,但你必须确保该bool变量将表现出某些特征,包括但不限于以下列表.
有不同的synchronization机制提供"更好的锁定"(跨进程与跨线程,多处理器对比,单处理器等),代价是"性能降低",所以你应该总是选择一个足够的锁定机制情况.
Fer*_*i-4 13
只是补充一下这里其他人所说的......
\nC++ 中有一个称为资源获取即初始化 (RAII) 的想法,即将资源绑定到对象的生命周期:
\n\n\n\n资源获取是初始化或 RAII,是一种 C++ 编程技术,它绑定在使用前必须获取的资源的生命周期(分配的堆内存、执行线程、打开套接字、打开文件、锁定互斥体、磁盘空间、数据库连接\ xe2\x80\x94 任何有限供应的东西)到对象的生命周期。
\n
类的使用std::lock_guard<std::mutex>遵循 RAII 思想。
为什么这有用?
\n考虑一下您不使用的情况std::lock_guard:
std::mutex m; // global mutex\nvoid oops() {\n m.lock();\n doSomething();\n m.unlock();\n}\nRun Code Online (Sandbox Code Playgroud)\n在这种情况下,将使用全局互斥锁,并在调用 之前将其锁定doSomething()。一旦doSomething()完成,互斥锁就会被解锁。
这里的一个问题是,如果出现异常怎么办?现在,您面临着永远无法到达将互斥锁释放给其他线程的行的风险m.unlock()。\n因此,您需要涵盖遇到异常的情况:
std::mutex m; // global mutex\nvoid oops() {\n try {\n m.lock();\n doSomething();\n m.unlock();\n } catch(...) {\n m.unlock(); // now exception path is covered\n // throw ...\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n这可行,但丑陋、冗长且不方便。
\n现在让我们编写自己的简单锁卫。
\nclass lock_guard {\nprivate:\n std::mutex& m;\npublic: \n lock_guard(std::mutex& m_):(m(m_)){ m.lock(); } // lock on construction\n ~lock_guard() { m.unlock(); }} // unlock on deconstruction\n}\nRun Code Online (Sandbox Code Playgroud)\n当lock_guard对象被销毁时,它将确保互斥锁被解锁。\n现在我们可以使用这个lock_guard以更好/更干净的方式处理之前的情况:
\nstd::mutex m; // global mutex\nvoid ok() {\n lock_guard lk(m); // our simple lock guard, protects against exception case \n doSomething(); \n} // when the scope is exited our lock guard object is destroyed and the mutex is unlocked\nRun Code Online (Sandbox Code Playgroud)\n这背后也是同样的想法std::lock_guard。
同样,这种方法可用于许多不同类型的资源,您可以通过 RAII 上的链接阅读更多信息。
\n