【问题标题】:Do I need to protect the read on a variable that is only modified in the same thread?我是否需要保护仅在同一线程中修改的变量的读取?
【发布时间】:2019-02-20 12:54:31
【问题描述】:

让我们考虑两个或更多线程和一个资源。如果这是相关的,我在 Ubuntu 上使用 C++11。 下面的代码说明了这种情况:

#include <thread>
#include <mutex>

class Res
{
    //Data
};

void use_resource(const Res& rsc) {/*Do stuff*/}
void modify_resource(Res& rsc) {/*Modify the resource*/}

class A
{
    Res resource;
    std::mutex resource_mtx;
    std::thread thd;

    public:
    A()
    {
        thd = std::thread(&A::loop,this);      
    }

    void loop()
    {
        while(true)
        {
            use_resource(resource); //(Case 1)

            //Some work

            {
                std::lock_guard<std::mutex> mlock(resource_mtx);
                modify_resource(resource); //(Case 2)
            }
        }
    }

    Res get_resource()
    {
        std::lock_guard<std::mutex> mlock(resource_mtx);
        return resource; //(Case 3)
    }
};

int main()
{
    A a;


    while(true)
    {
        Res res1 = a.get_resource();

        //Do stuff with the resource
    }
}

我们有一个包含一些数据的资源。 A 中的函数 loop() 仅在第一个线程上运行。其他线程可以调用 get_resource() 来访问资源。资源只能通过modified_resource函数修改。
我的理解是在情况 2 和 3 中需要加锁,因为情况 2 涉及写入,情况 3 涉及从另一个线程读取。

我想知道的是在情况 1 中是否需要锁。从cppreference.com 来看,数据竞争的定义似乎是:

当一个表达式的求值写入一个内存位置并且 另一个评估读取或修改相同的内存位置, 表达式被称为冲突。有两个冲突的程序 评估存在数据竞争,除非:
i) 两个评估在同一个线程或同一个信号处理程序中执行,或者
ii) 两个冲突的求值都是原子操作(见 std::atomic),或
iii)其中一个冲突的评估发生在另一个之前(参见 std::memory_order)

根据这些定义,我认为我的代码中的案例 1 没有数据竞争:
1) 和 2) 之间没有冲突(它们在同一个线程上,i) 适用)。
1) 和 3) 之间没有冲突(都不是写)。

这引出了我的两个问题:
Q1) 在提供的具体代码中,是否需要通过锁定 resource_mtx 来保护对 use_resource 的调用(案例 1)以避免数据竞争?
Q2) 如果这次案例 1 和 2 可以在 loop() 函数中以任何顺序或数量重复,同样的问题?一个任意的例子是:

while(true)
{
    {
        std::lock_guard<std::mutex> mlock(resource_mtx);
        modify_resource(resource); //(Case 2)
    }
    use_resource(resource); //(Case 1)      
    {
        std::lock_guard<std::mutex> mlock(resource_mtx);
        modify_resource(resource); //(Case 2)
    }
    use_resource(resource); //(Case 1)
    use_resource(resource); //(Case 1)
    {
        std::lock_guard<std::mutex> mlock(resource_mtx);
        modify_resource(resource); //(Case 2)
    }
}

如上所述,我的猜测是在这两种情况下都不需要锁,但我很可能会遗漏一些东西,比如编译器重新排序,甚至是数据竞争的实际定义。

到目前为止,我只看到有关来自单独线程的只读访问的问题,这是我的情况 2(therethere)或两个不同线程之间的交互(there),但我有在同一个线程中没有看到这个只读访问的具体问题。

编辑:编辑代码以实际反映多线程场景。

【问题讨论】:

  • 当您不执行多线程时,您的程序在单线程中运行。也许这已经回答了你的问题......
  • 一个线程一次只能做一件事。它不能同时读取和写入变量。
  • 也许我的例子当时还不清楚,对此感到抱歉。虽然循环确实在单个线程上运行,但还有其他线程可以访问在单独的线程上运行的 A 类。那些其他线程可以在循环运行时调用 get_resource
  • 顺便说一句,我并不想刻薄。不管我的评论如何,我认为这是一个完全有效的问题。正确同步数据绝非易事。我不得不承认,我没有时间详细阅读您的代码。如果你能把它变成minimal reproducible example
  • 你的标题也让我失望了。在你的情况你需要保护变量,因为它可以被不同的线程同时访问。

标签: c++ multithreading


【解决方案1】:

在您的示例中,您是“安全的”(假设正确的 Res)。

2)(写操作)与1)/2)3)(没有同步)同时执行时,就会出现问题。 1)3) 可能同时发生,只读。

1)2) 在同一个线程中,因此不能与 2) 同时发生。 3)2)mutex 保护。

请注意,如果在3) 中,您不创建副本而是返回引用,那么3) mutex 将不会扩展正确的范围。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-08-07
    • 1970-01-01
    • 2016-01-30
    • 2010-09-16
    • 2021-10-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多