【问题标题】:Keeping a thread locked a long time长时间锁定线程
【发布时间】:2020-03-29 10:10:50
【问题描述】:

编辑2:要明确我的问题是我正在努力编写代码来阻止和恢复线程来做一些工作(在这种情况下,读取一些文件)。

编辑 3:添加了 void Loader::RequestToLoadRegion(Region &region) 的函数定义。

我想在我的游戏中创建一个基本的流媒体系统。我需要一个线程来读取大部分时间会被阻塞并不时唤醒的游戏文件。这是我当前设计的工作原理。

每当玩家接近未加载到 RAM 中的世界区域时, 游戏向(唯一)Loader 对象发送请求以加载该区域(Loader 类代表流系统)。 每个区域都有一个与之关联的文件列表,这些文件将被读取以创建游戏数据并将其加载到 RAM 中。

加载分两步完成:

  • 第 1 步:读取所有文件并将其内容推送到队列中,
  • 第 2 步:处理存储在此队列中的原始文件数据。

Loader 对象使用 Reader 对象读取所有文件并将其内容推送到上述队列中。在 Loader 的构造函数中,构造了 Reader 对象,该对象启动了一个线程,该线程将用于读取文件。但是,这个读取线程大部分时间都会被阻塞,只有当 Loader 收到加载新区域的请求时才会被 Loader 唤醒。

在每一帧开始时,Loader 将获得控制权并检查其当前状态,无论是否加载。如果它处于加载状态,那么它将尝试访问受互斥体保护的队列。如果它从队列中获得一个项目,它将处理它并将控制权交还给游戏。队列中的下一项将在下一帧处理,以此类推,直到区域加载完成。

我知道如何使用互斥锁来实现这部分。但是,我有一些麻烦要保持读取线程被阻塞并在加载请求到达时唤醒它。我已经学会了如何将 std::condition_variable 用于基本程序,我目前的解决方案如下:

  • 在创建加载程序后,它会锁定一个互斥体,以便读取线程保持阻塞状态。
  • 当游戏向 Loader 发送请求时,后者将 Reader 的标志 (m_canRead) 设置为 true,并使用条件变量调用 notify_one() 以唤醒读取线程。
  • 当读取线程即将读取文件时,它会调用 m_loader.OnStartReading() 以便 Loader 锁定 m_canReadMutex 互斥锁。然后控制权返回给读取文件的阅读器。

    我在这个设计中遇到的最大问题是,我了解到您通常会在短时间内锁定互斥锁,并且通常将它与 std::lock_guard 一起使用,这样如果发生异常,互斥锁就会被解锁。

// This is the function ran by the reading thread
// m_readingThread = std::thread(&Reader::Run, this);
void Reader::Run() {
  while (true) {
    {
      std::unique_lock<std::mutex> ul(m_canReadMutex);
      m_canReadCondVar.wait(ul, [this](){ return m_quit || m_canRead; });
      // Determine the cause of the wake up
      if (m_quit) { // Does the game wants to quit? We break out of
    break;      // the loop to reach the end of the Run() function
      }             // that way we can join() the reading thread.

      // If control reaches this line, it means the thread was awaken by
      // a load request and can read. The closing bracket "will run the destructtor"
      // of the std::unique_lock to unlock the mutex.
    }

    { // When control reaches this scope, it means m_canRead is true.
      m_loader.OnStartReading(); // will lock the mutex m_canReadMutex
      ReadFiles();
    }
  }// end-while (true)
}// end-function void Run()

void Reader::ReadFiles() {
  /* Read all the files and push data into the queue */
}

void Loader::OnStartReading() {
  m_canReadMutex.lock();
}

// This function unlocks the mutex and wake up the reading thread.
void Loader::RequestToLoadRegion(Region &region) {
  /* Get the list of files associated to the region */
  m_canReadMutex.unlock();
  m_reader.m_canRead = true; // Set the flag so the predicate in the reader's wait() evaluates to true
  m_canReadCondVar.notify_one(); // Wake up the reader thread
}

【问题讨论】:

  • 我不确定你的问题是什么,即你的类/意图是复杂和模糊的,但是当锁定一个条件变量时,它持有的互斥锁在启动等待时被解锁。
  • 流系统由一个单例加载器表示。该对象使用一个 Reader 对象,该对象启动一个线程从磁盘读取文件,而主线程运行游戏循环。我的问题是编写停止并启动读取线程的代码。

标签: c++ multithreading


【解决方案1】:

std::unique_lockstd::lock_guard 非常相似:它在构造函数中获取锁并在析构函数中释放它。不同之处在于它还提供了额外的功能,其中之一是您可以将它与std::condition_variable::wait 一起使用。

当您调用wait 时,库会将线程标记为等待,然后解锁互斥锁并阻塞。当条件变量解除阻塞时(由于调用notify_onenotify_all 或虚假),然后在调用wait 返回之前重新锁定互斥锁。因此,互斥锁在wait 调用之前和之后都被锁定,但在wait 调用期间不会。

更新:

OnStartReading直接锁定互斥量,没有对应的解锁。这真的很不寻常:这意味着互斥体将永远被锁定,从此时开始。

我希望onStartReading 使用std::lock_guard 来锁定互斥锁,以确保之后解锁。如果您需要为整个ReadFiles 锁定它,那么OnStartReading 将需要使用unique_lock,并返回它,或者将ReadFiles 移动到OnStartReading

进一步更新:

添加的代码越多,看起来越糟糕:(

您不能在一个线程中锁定一个互斥体并在另一个线程中解锁它。

作为一般规则,您应该永远不要在互斥体上直接调用 lockunlock。如果可以,只需使用lock_guard,并将锁包含在单个范围内。如果您需要使用带有条件变量等待的锁,请使用unique_lock。如果您必须在一个范围内锁定并在另一个范围内解锁,请使用unique_lock 并将其作为参数作为返回值传递。

在设置m_canRead 或设置m_quit 之前,对m_canReadCondVar.wait 的调用不会返回,并且它可以获得互斥锁。如果另一个线程持有互斥锁(暂时,因为你应该只持有很短的时间),那么等待就不能返回。

RequestToLoadRegion 是错误的。它应该设置标志,然后解锁互斥锁,而不是相反,否则m_canRead 上存在数据竞争,以及未定义的行为。但是,这里的互斥解锁无论如何都不能与OnStartReading中的锁配对,除非它们在同一个线程中。

【讨论】:

  • 当我读到条件变量时,我就是这么理解的。我的问题是我不确定我的代码是否有问题。
  • 我在最后添加了函数 void Loader::RequestToLoadRegion(Region &region) 的定义。当 Reader 读取完所有文件后,必须锁定互斥锁,以便读取线程仅在必须时工作。
  • 我想知道如何使用 lock_guard 或 unique_lock。当没有要读取的文件时,我不希望读取线程运行。但是当它被 wait() 阻塞时,我猜当且仅当互斥锁都没有锁定并且谓词返回 true 时它才会恢复。
【解决方案2】:

你的代码很好。这是我等待值的标准代码:

void
WaitCondition::wait() {
    unique_lock<std::mutex> lock(myMutex);
    while (!value) {
        condVar.wait(lock);
    }
}

等待 condVar 实际上会释放锁,直到等待完成。所以你很好。

(根据雷蒙德的建议编辑。)

【讨论】:

  • 你想把锁放在循环之外。这样,在锁下检查该值。您可以使用谓词版本一次性完成所有操作:unique_lock&lt;std::mutex&gt; lock(myMutex); condVar.wait(lock, [] { return value != 0; });
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多