多线程竞争条件问题

时间:2017-06-24 14:20:45

标签: c++ multithreading c++11 race-condition c++17

我的一些代码遇到了一些多线程问题。 ManagedObject类实现“延迟初始化”,它使用Initialize方法初始化其状态。每个访问者都会调用Initialize。这是因为初始化对于性能来说可能是非常昂贵的。

现在在单线程环境中,我的实现在下面没有问题,但在我目前的情况下,它可以从多个线程访问,因此它们可以同时启动初始化过程。

每秒60-100次失效并在其他某个线程尝试从托管对象访问数据时再次执行初始化过程。因为多个线程可以在同一个对象上请求数据,所以初始化可能会重叠并使事情变得非常糟糕。

如果有人能指出我在这里的一些最佳做法,我真的很感激!

#include <iostream>
#include <windows.h>

#include <thread>
#include <atomic>
#include <string>
#include <mutex>

using namespace std;

class ManagedObject
{
protected:
    std::atomic<bool> initialized = false;

public:
    void Initialize(std::string name)
    {
        if (initialized) return;

        // this code should only be ran once. Since initialized can still be false, other threads may start initializing as well, this should not happen.
        Sleep(500);
        cout << name << ": Initializing 1" << endl << endl;
        Sleep(500);

        initialized = true;
    }

    void Invalidate()
    {
        initialized = false;
    }

    bool IsActive(std::string name)
    {
        Initialize(name);
        return true;
    }
};

int main()
{
    auto object1 = make_shared<ManagedObject>();

    std::thread([&] {
        object1->IsActive("Thread 1");
    }).detach();

    std::thread([&] {
        object1->IsActive("Thread 2");
    }).detach();

    Sleep(5000);

    return 0;
}

该程序的输出是:

Thread 1: Initializing 1

Thread 2: Initializing 1

预期输出应仅为一个线程初始化,而另一个则等待初始化状态而不进行初始化过程本身。

2 个答案:

答案 0 :(得分:0)

对我来说,这似乎是一种经典的竞争条件。可以使用IsActive()Initialize()内的互斥锁轻松解决。

喜欢这样

bool IsActive(std::string name)
{
    initMutex.lock();
    Initialize(name);
    initMutex.unlock();
    return true;
}

其中initMutex是类ManagedObject的私有变量或全局变量。

在评论中,您声明:

  

我不确定互斥锁是否会在这里提供帮助,因为它会阻止执行,而不是阻止它。 Initialize()方法只应在initialized = false

时运行

如果没有互斥锁,则可能存在initialized = false

的多个实例

答案 1 :(得分:0)

我最终像这样实现它,这对我来说非常好。由于互斥锁,它可能不是最快的方式,但这是我现在能想到的最好的方法。

class ManagedObject
{
protected:
  std::mutex initMutex;
  bool initialized = false;
  bool isInitializing = false;

  virtual void DoInitialize() {}

  virtual void Initialize()
  {
    if (initialized) return;

    initMutex.lock();
    if (!isInitializing)
    {
        isInitializing = true;
        DoInitialize();
        isInitializing = false;
    }
    initMutex.unlock();

    initialized = true;
  }

public:
  virtual void Invalidate()
  {
    initialized = false;
  }
}    

class Player : public ManagedObject
{
public:
  void DoInitialize()
  {
     // initialize its members here.
  }

  bool DoSomethingUseful()
  {
      Initialize();
      return true; // use some member here
  }
}