我需要保护仅在同一线程中修改的变量的读取吗?

时间:2019-02-20 12:54:32

标签: c++ multithreading

让我们考虑两个或多个线程和一个资源。如果相关,我在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)是否可以在 loop()函数中以任意顺序或数字重复这种情况1和2?一个任意的例子是:

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)或两个不同线程之间的交互({{3} }),但我还没有看到在同一线程中进行只读访问的特定问题。

编辑:编辑代码以实际反映多线程方案。

1 个答案:

答案 0 :(得分:2)

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

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

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

请注意,如果您不在3)中创建副本但返回引用,则3) mutex不会被扩展为正确的范围。