编辑 2:需要明确的是,我的问题是我正在努力编写代码来阻止和恢复线程来完成一些工作(在本例中,读取一些文件)。
编辑 3:添加了 void Loader::RequestToLoadRegion(Region ®ion) 的函数定义。
我想在我的游戏中创建一个基本的流媒体系统。我需要一个线程来读取大部分时间会被阻塞并不时唤醒的游戏文件。这是我当前设计的工作原理。
每当玩家接近世界上未加载到 RAM 中的区域时, 游戏向(唯一的)Loader 对象发送请求以加载该区域(Loader 类代表流式传输系统)。 每个区域都有一个与其关联的文件列表,这些文件将被读取以创建游戏数据并将其加载到 RAM 中。
加载分两步完成:
- 第 1 步:读取所有文件并将其内容推送到队列中,
- 第 2 步:处理存储在此队列中的原始文件数据。
Loader 对象使用 Reader 对象读取所有文件并将其内容推送到上述队列中。在 Loader 的构造函数中,构造了 Reader 对象,它启动了一个线程,用于读取文件。然而,这个读取线程大部分时间会被阻塞,只有当 Loader 收到加载新区域的请求时才会被 Loader 唤醒。
在每一帧的开始,Loader 将获得控制权并检查其当前状态,是否正在加载。如果它处于加载状态,那么它将尝试访问受互斥锁保护的队列。如果它从队列中获得一个项目,它将处理它并将控制权交还给游戏。队列中的下一项将在下一帧处理,依此类推,直到区域加载完成。
我知道如何使用互斥锁来实现这部分。但是,我在保持读取线程阻塞并在加载请求到达时唤醒它时遇到了一些麻烦。我已经学会了如何将 std::condition_variable 用于基本程序,我当前的解决方案如下:
- 在创建 Loader 后,它会锁定一个互斥锁,以便读取线程保持阻塞状态。
- 当游戏向 Loader 发送请求时,Loader 将 Reader 的标志 (m_canRead) 设置为 true 并使用条件变量调用 notify_one() 以唤醒读取线程。
当读取线程即将读取文件时,它会调用 m_loader.OnStartReading() 以便加载程序锁定 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 ®ion) {
/* 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
}
最佳答案
std::unique_lock
与 std::lock_guard
非常相似:它在构造函数中获取锁并在析构函数中释放它。不同之处在于它还提供了额外的功能,其中之一是您可以将它与 std::condition_variable::wait
一起使用。 .
当您调用 wait
时,然后库将线程标记为等待,然后解锁互斥锁和 block 。当条件变量解锁时(由于调用 notify_one
或 notify_all
或虚假),它会在调用 wait
之前重新锁定互斥锁。返回。因此互斥量在 wait
之前和之后都被锁定。打电话,但不在 wait
期间打电话。
更新:
OnStartReading
直接锁定互斥锁,没有相应的解锁。这真的很不寻常:这意味着互斥锁将从此时开始永远锁定。
我希望 onStartReading
使用 std::lock_guard
锁定互斥锁,以确保之后解锁。如果您需要将其锁定整个 ReadFiles
, 然后 OnStartReading
将需要使用 unique_lock
,然后返回,或者 ReadFiles
搬进OnStartReading
.
进一步更新:
添加的代码越多,看起来越糟糕:(
您不能在一个线程中锁定互斥量并在另一个线程中解锁它。
作为一般规则,您应该永远不要调用lock
或 unlock
直接在互斥锁上。如果可以,就用lock_guard
,并包含对单个范围的锁定。如果需要使用条件变量等待的锁,使用unique_lock
.如果您必须锁定一个范围,并在另一个范围解锁,请使用 unique_lock
并将其作为参数作为返回值传递。
调用m_canReadCondVar.wait
直到 m_canRead
才会返回已设置,或 m_quit
已设置,并且它可以获得互斥锁。如果另一个线程持有互斥量(暂时的,因为您应该只持有锁很短的时间),则等待无法返回。
RequestToLoadRegion
是错误的。它应该设置标志,然后解锁互斥量,而不是相反,否则在 m_canRead
上会发生数据竞争。和未定义的行为。但是这里的mutex unlock不能和OnStartReading
里面的锁配对无论如何,除非他们在同一个线程中。
关于c++ - 长时间保持线程锁定,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59178871/