将线程锁定很长时间
Keeping a thread locked a long time
编辑2:要澄清的是,我的问题是,我很难写代码来阻止和恢复线程来做一些工作(在这种情况下,读取一些文件)。
编辑3:添加了void Loader::RequestToLoadRegion(Region&Region)的函数定义。
我想在我的游戏中创建一个基本的流媒体系统。我需要一个线程来读取游戏文件,这些文件大部分时间都会被屏蔽,并不时唤醒。以下是我目前的设计方法。
每当玩家接近未加载在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(),以便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 ®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
时,库会将线程标记为等待,然后解锁互斥锁和块。当条件变量解除锁定时(由于对notify_one
或notify_all
的调用或错误调用),它会在对wait
的调用返回之前重新锁定互斥对象。因此,互斥锁在wait
调用之前和之后都被锁定,但在wait
调用期间不被锁定。
更新:
OnStartReading
直接锁定互斥,没有相应的解锁。这真的很不寻常:这意味着互斥锁将永远被锁定,从这一点开始。
我希望onStartReading
使用std::lock_guard
来锁定互斥锁,以确保它在之后被解锁。如果您需要在整个ReadFiles
中锁定它,那么OnStartReading
将需要使用unique_lock
并返回它,或者ReadFiles
移动到OnStartReading
中。
进一步更新:
添加的代码越多,情况看起来就越糟糕:(
不能在一个线程中锁定互斥对象,在另一个线程中将其解锁
一般来说,您应该永远不要直接在互斥对象上调用lock
或unlock
。如果可以,只需使用lock_guard
,并将锁包含在单个作用域中。如果需要将锁与条件变量wait一起使用,请使用unique_lock
。如果必须在一个作用域中锁定,在另一个作用区中解锁,请使用unique_lock
并将其作为参数作为返回值传递。
对m_canReadCondVar.wait
的调用将不会返回,直到m_canRead
被设置,或者m_quit
被设置,和它可以获取互斥锁。如果另一个线程持有互斥锁(暂时的,因为您应该只持有锁很短的时间),那么等待就无法返回。
RequestToLoadRegion
是错误的。它应该设置标志,然后解锁互斥,而不是相反,否则m_canRead
上会出现数据竞争和未定义的行为。然而,这里的互斥解锁无论如何都不能与OnStartReading
中的锁配对,除非它们在同一线程中。
您的代码很好。这是我等待值的标准代码:
void
WaitCondition::wait() {
unique_lock<std::mutex> lock(myMutex);
while (!value) {
condVar.wait(lock);
}
}
等待condVar实际上会释放锁,直到等待完成。所以你很好。
(根据Raymond的建议编辑。)
- 使用Boost Interprocess创建托管共享内存需要很长时间
- SFML RenderWindow打开窗口需要很长时间
- Kafka C++客户端需要很长时间才能收到消息
- 给定使用 C++ 或 C,我如何测量在 linux 下进行线程切换需要多长时间?可能吗?
- asio::read() 需要很长时间,使用 asio::write 没有问题
- 将线程锁定很长时间
- 正在等待在非阻塞文件描述符上长时间运行ioctl
- 即使长时间等待,C++线程也不会加入
- 连接() 在连接被拒绝时长时间挂起
- 为什么这段代码需要这么长时间才能用 g++ 编译?
- 如何在长时间运行的方法中等待信号?
- mbed 套接字连接需要很长时间
- Lambda 捕获此函数和长时间运行的函数
- std::fstream 需要很长时间才能将大数据写入.csv文件中
- 为什么与Java和Python相比,使用Cmake运行C++程序每次都需要这么长时间?
- 需要很长时间
- 在关闭应用程序期间正确关闭线程,该线程可能会运行很长时间的循环
- 如何在长时间计算中进行C 的一次进度更新
- gai_cancel() 需要很长时间才能成功
- 您怎么知道长时间的长度