同步非常快的线程
Synchronizing very fast threads
在下面的示例(理想化的"游戏"(中,有两个线程。更新数据的主线程,RenderThread
将其"呈现"到屏幕上。我需要它这两个同步。我无法承受运行多个更新迭代而不为每个更新迭代运行渲染。
我使用condition_variable
来同步这两个线程,因此理想情况下,较快的线程将花费一些时间等待较慢的线程。但是,如果其中一个线程在非常短的时间内完成迭代,条件变量似乎无法完成这项工作。它似乎在另一个线程中的wait
能够获取互斥锁之前快速重新获取锁。即使notify_one
被称为
#include <iostream>
#include <thread>
#include <chrono>
#include <atomic>
#include <functional>
#include <mutex>
#include <condition_variable>
using namespace std;
bool isMultiThreaded = true;
struct RenderThread
{
RenderThread()
{
end = false;
drawing = false;
readyToDraw = false;
}
void Run()
{
while (!end)
{
DoJob();
}
}
void DoJob()
{
unique_lock<mutex> lk(renderReadyMutex);
renderReady.wait(lk, [this](){ return readyToDraw; });
drawing = true;
// RENDER DATA
this_thread::sleep_for(chrono::milliseconds(15)); // simulated render time
cout << "frame " << count << ": " << frame << endl;
++count;
drawing = false;
readyToDraw = false;
lk.unlock();
renderReady.notify_one();
}
atomic<bool> end;
mutex renderReadyMutex;
condition_variable renderReady;
//mutex frame_mutex;
int frame = -10;
int count = 0;
bool readyToDraw;
bool drawing;
};
struct UpdateThread
{
UpdateThread(RenderThread& rt)
: m_rt(rt)
{}
void Run()
{
this_thread::sleep_for(chrono::milliseconds(500));
for (int i = 0; i < 20; ++i)
{
// DO GAME UPDATE
// when this is uncommented everything is fine
// this_thread::sleep_for(chrono::milliseconds(10)); // simulated update time
// PREPARE RENDER THREAD
unique_lock<mutex> lk(m_rt.renderReadyMutex);
m_rt.renderReady.wait(lk, [this](){ return !m_rt.drawing; });
m_rt.readyToDraw = true;
// SUPPLY RENDER THREAD WITH DATA TO RENDER
m_rt.frame = i;
lk.unlock();
m_rt.renderReady.notify_one();
if (!isMultiThreaded)
m_rt.DoJob();
}
m_rt.end = true;
}
RenderThread& m_rt;
};
int main()
{
auto start = chrono::high_resolution_clock::now();
RenderThread rt;
UpdateThread u(rt);
thread* rendering = nullptr;
if (isMultiThreaded)
rendering = new thread(bind(&RenderThread::Run, &rt));
u.Run();
if (rendering)
rendering->join();
auto duration = chrono::high_resolution_clock::now() - start;
cout << "Duration: " << double(chrono::duration_cast<chrono::microseconds>(duration).count())/1000 << endl;
return 0;
}
这是这个小示例代码的源代码,正如你所看到的,即使在 ideone 的运行时,输出也是frame 0: 19
的(这意味着渲染线程完成了一次迭代,而更新线程已经完成了所有 20 次迭代(。
如果我们取消注释第 75 行(即模拟更新循环的一些时间(,一切正常。每个更新迭代都有一个关联的渲染迭代。
有没有办法真正同步这些线程,即使其中一个线程在短短几纳秒内完成迭代,但如果它们都需要一些合理的毫秒才能完成,也不会对性能造成影响?
如果我理解正确,您希望 2 个线程交替工作:更新器等到渲染器完成再再次迭代,渲染器等到更新器完成再迭代。部分计算可以是并行的,但两者之间的迭代次数应相似。
您需要 2 把锁:
- 一个用于更新
- 一个用于渲染
更新:
wait (renderingLk)
update
signal(updaterLk)
渲染:
wait (updaterLk)
render
signal(renderingLk)
编辑:
即使看起来很简单,也有几个问题需要解决:
允许并行进行部分计算:如上面的代码片段所示,更新和渲染不会是并行的,而是顺序的,因此拥有多线程没有任何好处。对于真正的解决方案,一些计算应该在等待之前进行,并且只有新值的副本需要在等待和信号之间。渲染也是如此:所有渲染都需要在信号之后进行,并且只获取等待和信号之间的值。
实现还需要关注初始状态:因此在第一次更新之前不执行渲染。
两个线程的终止:因此在另一个线程终止后,没有人会保持锁定或无限循环。
我认为互斥锁(单独(不是适合这项工作的工具。您可能需要考虑改用信号量(或类似的东西(。你所描述的听起来很像生产者/消费者的问题,即,每当一个进程完成任务时,允许另一个进程运行一次。因此,您还可以查看生产者/消费者模式。例如,本系列可能会为您提供一些想法:
- 具有 C++11 的多线程生产者使用者
在那里,std::mutex
与std::condition_variable
相结合,以模仿信号量的行为。一种看起来相当合理的方法。您可能不会上下计数,而是切换真假变量,需要重绘语义。
供参考:
- http://en.cppreference.com/w/cpp/thread/condition_variable
- C++0x 没有信号量?如何同步线程?
这是因为您使用了一个单独的drawing
变量,该变量仅在渲染线程在wait
后重新获取互斥锁时设置,这可能为时已晚。当删除drawing
变量并将更新线程中的wait
检查替换为! m_rt.readyToDraw
时,问题就消失了(更新线程已经设置了,因此不容易受到逻辑争用的影响(。
修改后的代码和结果
也就是说,由于线程不并行工作,因此我并不真正理解拥有两个线程的意义。除非以后选择实现双重(甚至三重(缓冲。
计算机图形学中经常使用的一种技术是使用双缓冲区。每个渲染器和生产者都拥有自己的缓冲区,而不是让渲染器和生成者对内存中的相同数据进行操作。这是通过使用两个独立的缓冲区来实现的,并在需要时切换它们。生成器更新一个缓冲区,完成后,它会切换缓冲区并用下一个数据填充第二个缓冲区。现在,当生产者处理第二个缓冲区时,渲染器会处理第一个缓冲区并显示它。
您可以通过让渲染器锁定交换操作来使用此技术,以便生成者可能必须等到渲染完成。
- 从不同线程使用int64的不同字节安全吗
- 删除一个线程上有数百万个字符串的大型哈希映射会影响另一个线程的性能
- 在C++中使用cURL和多线程
- 为什么我的C#代码在调用回C++COM直到Task时会暂停.等待/线程.加入
- 在cuda线程之间共享大量常量数据
- 如何将元素添加到数组的线程安全函数?
- 线程,如果else语句,都是错误的上下文切换后,会发生什么
- C++Boost Asio Pool线程,带有lambda函数和传递引用变量
- Qt C++静态thread_local QNetworkAccessManager是线程应用程序的好选择吗
- 异常属于C++中的线程还是进程
- C++中的线程安全删除
- C++使用params创建线程函数会导致转换错误
- 类与私有变量的其他类之间的线程安全性
- CoInitialize()在单独的线程上崩溃而不返回
- 在多线程环境中使用 libcurl 会导致与 DNS 查找相关的性能非常慢
- C 11多线程在神经网络中的性能非常缓慢
- 为什么这个非常简单的返回 std::move(线程句柄)失败
- 对C++多线程感到非常困惑
- 同步非常快的线程
- c++非常简单的线程