首页 新闻 会员 周边

请教大佬有关这段C++的DCLP(Double-Checked Locking Pattern)代码,有几个问题不太确定

0
悬赏园豆:30 [待解决问题]
std::atomic<Singleton*> Singleton::m_instance;
std::mutex Singleton::m_mutex;

Singleton* Singleton::getInstance() {
    Singleton* tmp = m_instance.load(std::memory_order_acquire);  // 第一个load
    if (tmp == nullptr) {
        std::lock_guard<std::mutex> lock(m_mutex);               // 获取锁
        tmp = m_instance.load(std::memory_order_relaxed);        // 第二个load
        if (tmp == nullptr) {
            tmp = new Singleton;
            m_instance.store(tmp, std::memory_order_release);
        }
    }
    return tmp;
}
  1. 不同线程中 对同一个mutex的unlock、lock操作之间 是否一定存在“Synchronizes-With”关系?
  2. 代码段中的第二个load的参数是否有必要换为std::memory_order_acquire?
  3. 假设现有三个线程A、B、C,且三者都执行到了lock(m_mutex),接下来 线程A 成功lock,创建了Singleton实例,并在m_instance上执行了store操作;待 线程A释放锁之后,线程B、C也会获取锁,走到代码段中的第二个load,这时线程B、C的这个 load-relaxed 会与线程A的store-release形成“Synchronizes-With”关系吗?线程B、C是如何load到线程A写入的m_instance值的?是因为对mutex的unlock、lock操作之间保证了线程间数据的可见性吗?
Avocados的主页 Avocados | 菜鸟二级 | 园豆:372
提问于:2024-07-18 10:35
< >
分享
所有回答(2)
0

不同线程中对同一个mutex的unlock、lock操作之间是否一定存在“Synchronizes-With”关系?

是的,在C++标准中,std::mutex的unlock和lock操作之间存在“Synchronizes-With”关系。具体来说,当一个线程解锁(unlock)一个std::mutex,并且另一个线程随后锁定(lock)同一个std::mutex,则这两个操作之间存在同步关系,这意味着所有在第一个线程解锁之前的操作对第二个线程可见。因此,对同一个std::mutex的解锁和加锁操作之间一定存在“Synchronizes-With”关系。

代码段中的第二个load的参数是否有必要换为std::memory_order_acquire?

不需要。第二个load使用std::memory_order_relaxed是合理的,因为在前面已经有一个锁保护的区域,并且在创建实例时已经使用了std::memory_order_release进行存储操作。在这里,std::memory_order_relaxed足够,因为在锁保护的区域内,所有的操作已经保证了顺序性和可见性。

线程A释放锁之后,线程B、C也会获取锁,走到代码段中的第二个load,这时线程B、C的这个 load-relaxed 会与线程A的store-release形成“Synchronizes-With”关系吗?线程B、C是如何load到线程A写入的m_instance值的?是因为对mutex的unlock、lock操作之间保证了线程间数据的可见性吗?

是的,线程A释放锁之后,线程B、C获取锁并走到第二个load操作。这时,虽然第二个load是memory_order_relaxed,但由于锁的操作已经保证了内存的可见性。具体来说,当线程A执行store操作并释放锁之后,线程B和C在获取锁并进入临界区时,它们能看到所有由线程A发布的修改。这是因为std::mutex的lock和unlock操作之间有“Synchronizes-With”关系,保证了线程A在释放锁之前的所有操作对B和C可见。

bigliuzi | 园豆:202 (菜鸟二级) | 2024-07-18 14:59
0

来自AI

对于问题1:对同一个互斥量的unlock和lock操作之间,是具有Synchronizes-With关系的。Synchronizes-With关系确保了一对操作之间的顺序一致性,即在该关系下,前一个操作发生在后一个操作之前。
对于问题2:在代码段中的第二个load的参数是否需要换成std::memory_order_acquire,答案是可以不用换。在这种情况下,std::memory_order_relaxed是足够的。因为在这里,第一个load采用了std::memory_order_acquire,以确保对于tmp的加载是原子的,并且在数据的读取之前,其之前的操作都会完成。而第二个load只需要保证数据的一致性,而不需要保证之前的操作。
对于问题3:在多线程环境下,对于同一个互斥量的lock操作形成了happens-before关系,因此线程B和C可以看到线程A在m_instance上的store操作。由于先前的happens-before关系,线程B和C的load-relaxed与线程A的store-release之间形成了Synchronizes-With关系,确保了数据的可见性。
需要注意的是,在C++11之前,DCLP(Double-Checked Locking Pattern)是不可靠的,并且可能存在竞争条件。在C++11及之后的版本中,通过使用std::memory_order_acquire和std::memory_order_release等内存顺序标记,可以保证正确的行为。

Technologyforgood | 园豆:7199 (大侠五级) | 2024-07-18 16:42
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册