简体   繁体   English

C ++并发编程中的双重检查锁定模式

[英]double checked locking pattern in c++ concurrent programming

I am reading concurrency programming in c++ and came across this piece of code. 我正在阅读C ++中的并发编程,并遇到了这段代码。 the book mentioned the potential for nasty race conditions. 这本书提到了令人讨厌的种族状况的可能性。

void undefined_behaviour_with_double_checked_locking(){

if(!resource_ptr){        //<1>
    std::lock_guard<std::mutex> lk(resource_mutex);
    if(!resource_ptr){        //<2>   
        resource_ptr.reset(new some_resource);        //<3>
    }
}

resource_ptr->do_something();        //<4>

}

here is the quote of explanation from the book. 这是书中的解释语录。 however, i just cant come up with a real example. 但是,我只是无法提出一个真实的例子。 I wonder if anyone here could help me out. 我想知道这里是否有人可以帮助我。

Unfortunately, this pattern is infamous for a reason: it has the potential for nasty race conditions, because the read outside the lock <1> isn't synchronized with the write done by another thread inside the lock <3>. 不幸的是,这种模式之所以臭名昭著,是因为它有潜在的恶劣竞争条件,因为锁<1>外部的读取与锁<3>内另一个线程的写入不同步。 This therefore creates a race condition that covers not just the pointer itself but also the object pointed to; 因此,这将创建一个竞争条件,该条件不仅覆盖指针本身,还覆盖所指向的对象。 even if a thread sees the pointer written by another thread, it might not see the newly created instance of some_resource, resulting in the call to do_something() <4> operating on incorrect values. 即使一个线程看到了另一个线程编写的指针,也可能看不到新创建的some_resource实例,从而导致对do_something()<4>的调用操作了错误的值。

You don't show what resource_ptr is but from the explanation the reasoning seems to be that "!resource_ptr" (outside the lock) and "resource_ptr.reset" (inside the lock) are not atmoic and are not synchronized with each other. 您没有显示resource_ptr是什么,但是从解释来看,原因似乎是“!resource_ptr”(在锁之外)和“ resource_ptr.reset”(在锁内)没有气氛并且彼此不同步。

The use case would be: 用例为:

  1. thread1 comes into the method, sees that resource_ptr is not populated, enters the lock and is in the middle of the resource_ptr.reset. thread1进入该方法,看到未填充resource_ptr,进入了锁并位于resource_ptr.reset的中间。
  2. thread2 comes into the method and is when checking !resource_ptr may see it as set but resource_ptr may not be fully configured for use. thread2进入该方法,并且在检查!resource_ptr时可能会将其视为已设置,但resource_ptr可能未完全配置为可使用。
  3. thread2 falls through to execute "resource_ptr->do_something()" and may see resource_ptr in an inconsistent state and bad things may happen. thread2无法执行“ resource_ptr-> do_something()”,并且可能会看到resource_ptr处于不一致状态,并且可能会发生不良情况。

I recommend you read this: http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf . 我建议您阅读: http : //www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf

Anyway, the gist of it is: the compiler is free to reorder operations as long as they appear to be executed in the program's order in a single threaded situation . 无论如何,其要旨是:只要看起来在单线程情况下按照程序的顺序执行操作,编译器就可以自由地对其进行重新排序。 On top of that, some CPU architectures take the same liberties with their instruction execution order. 最重要的是,某些CPU体系结构在指令执行顺序方面享有相同的自由。 So, technically resource_ptr could be modified to point to newly allocated memory before some_resource's constructor has finished. 因此,从技术上讲,可以在some_resource的构造函数完成之前将resource_ptr修改为指向新分配的内存。 Another thread could at that time see that resource_ptr is not null and attempt to use the not-yet-fully-constructed instance. 当时,另一个线程可能会看到resource_ptr不为null,并尝试使用尚未完全构建的实例。

The use of a smart pointer instead of a raw pointer might make this less likely, but it doesn't rule it out afaik. 使用智能指针而不是原始指针可能会降低这种可能性,但是并不排除这种可能性。

The potential problem is that the write to resource_ptr isn't atomic (inside the reset call). 潜在的问题是对resource_ptr的写入不是原子的(在reset调用内部)。 Assuming that resource_ptr is a global or static variable that (/ or otherwise) starts initialized with the value NULL before we get here, it will never cause a thread to fall-through unless the object some_resource is already fully allocated and constructed, however - say that the pointer to this new object is 0x123456789, then it is theoretically possible that resource_ptr has, for example, the value 0x12340000 when another thread does the if (!resource_ptr) test, falls through and uses that value (especially more likely when using aliasing). 假设resource_ptr是一个全局变量或静态变量,并且(/或其他方式)在我们到达此处之前以值NULL初始化,那么它将永远不会导致线程崩溃,除非对象some_resource已经被完全分配和构造。指向该新对象的指针是0x123456789,则理论上很有可能resource_ptr的值是0x12340000 ,例如当另一个线程执行if (!resource_ptr)测试时,该值会掉下来并使用该值(尤其是在使用别名时更可能) )。 If resource_ptr is an atomic variable then this code would be fine. 如果resource_ptr是一个原子变量,那么此代码就可以了。

If a program can guarantee that the first time this code is called there is only one thread running (ie, the first call will be from main() before any other thread is created) then this will work fine too, because once initialized, the if test will just always fall through, resulting in only read accesses to resource_ptr while more than one thread is running. 如果程序可以保证首次调用此代码,则只有一个线程在运行(即,第一次调用将在创建任何其他线程之前从main()进行),那么这也可以正常工作,因为一旦初始化, if测试将始终失败,则导致在多个线程运行时仅对resource_ptr进行读取访问。 In that case you don't need the lock inside the if block though, and you are not allowed to ever write to resource_ptr anywhere else. 在那种情况下,尽管您不需要if块内的锁,也不允许您其他任何地方写入resource_ptr

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM