C++ 中的线程间通信
我有两个线程(应用程序主线程和另一个线程)。我正在使用 OpenGL 来绘制一些东西,并且我正在使用 OpenGL 键盘和鼠标回调。当我调用 glutMainLoop() 时,OpenGL 会阻塞,并且由于我必须在后台进行一些计算,因此我创建了另一个线程。现在,OpenGL 回调应将一些数据(例如,已按下的鼠标/键的 x、y 位置)发送到具有关键部分的另一个线程。当关键部分正在运行时,不应接受任何消息,但我不想删除这些消息,而是想在关键部分之后处理它们。非 OpenGL 的类看起来像这样:
void run()
{
for (;;) {
int currentTime = now();
if (now() - previousTime > WAIT_INTERVAL) {
previousTime = currentTime;
tick();
}
}
}
void tick() {
// critical section begins
processor->step()
// critical section ends
}
void receiveMessage(void *data) {
processor->changeSomeData(data);
}
因此,如果从 OpenGL 线程调用 receiveMessage() 并且处理器->step() 正在运行,则应推迟对changeSomeData() 的调用,因为它会弄乱整个计算。
我想使用以下类来同步线程:
Mutex.h:
#ifndef MUTEX_H
#define MUTEX_H
#include <Windows.h>
class Mutex;
#include "Lock.h"
class Mutex
{
public:
Mutex();
~Mutex();
private:
void acquire();
void release();
CRITICAL_SECTION criticalSection;
friend class Lock;
};
#endif
Mutex.cpp:
#include "Mutex.h"
Mutex::Mutex()
{
InitializeCriticalSection(&this->criticalSection);
}
Mutex::~Mutex()
{
DeleteCriticalSection(&this->criticalSection);
}
void Mutex::acquire()
{
EnterCriticalSection(&this->criticalSection);
}
void Mutex::release()
{
LeaveCriticalSection(&this->criticalSection);
}
Lock.h:
#ifndef LOCK_H
#define LOCK_H
class Lock;
#include "Mutex.h"
class Lock
{
public:
Lock(Mutex& mutex);
~Lock();
private:
Mutex &mutex;
};
#endif
Lock.cpp
#include "Lock.h"
Lock::Lock(Mutex& mutex) : mutex(mutex)
{
this->mutex.acquire();
}
Lock::~Lock ()
{
this->mutex.release();
}
编辑:
这是整个项目: http://upload.visusnet.de/uploads/BlobbyWarriors-rev30.zip (~180 MB)
编辑2:
这是SVN存储库:https://projects.fse.uni-due.de/svn/alexander-mueller-bloby-warriors/trunk/
I have two threads (the applications main thread and another one). I am using OpenGL to draw some stuff and I am using the OpenGL keyboard and mouse callbacks. OpenGL blocks when I call glutMainLoop() and since I have to do some calculations in the background, I created another thread. Now, the OpenGL callbacks shall send some data (e.g. x, y position of the mouse/key which has been pressed) to the other thread which has a critical section. While the critical section is running no messages should be accepted, but rather than dropping these messages, I want to process them after the critical section. The class of the non-OpenGL looks something like this:
void run()
{
for (;;) {
int currentTime = now();
if (now() - previousTime > WAIT_INTERVAL) {
previousTime = currentTime;
tick();
}
}
}
void tick() {
// critical section begins
processor->step()
// critical section ends
}
void receiveMessage(void *data) {
processor->changeSomeData(data);
}
So, if receiveMessage() is called from the OpenGL thread and processor->step() is running, the call to changeSomeData() should be postponed because it would mess up the whole calculation.
I want to use the following classes to synchronize the threads:
Mutex.h:
#ifndef MUTEX_H
#define MUTEX_H
#include <Windows.h>
class Mutex;
#include "Lock.h"
class Mutex
{
public:
Mutex();
~Mutex();
private:
void acquire();
void release();
CRITICAL_SECTION criticalSection;
friend class Lock;
};
#endif
Mutex.cpp:
#include "Mutex.h"
Mutex::Mutex()
{
InitializeCriticalSection(&this->criticalSection);
}
Mutex::~Mutex()
{
DeleteCriticalSection(&this->criticalSection);
}
void Mutex::acquire()
{
EnterCriticalSection(&this->criticalSection);
}
void Mutex::release()
{
LeaveCriticalSection(&this->criticalSection);
}
Lock.h:
#ifndef LOCK_H
#define LOCK_H
class Lock;
#include "Mutex.h"
class Lock
{
public:
Lock(Mutex& mutex);
~Lock();
private:
Mutex &mutex;
};
#endif
Lock.cpp
#include "Lock.h"
Lock::Lock(Mutex& mutex) : mutex(mutex)
{
this->mutex.acquire();
}
Lock::~Lock ()
{
this->mutex.release();
}
EDIT:
Here is the whole project: http://upload.visusnet.de/uploads/BlobbyWarriors-rev30.zip (~180 MB)
EDIT 2:
And here is the SVN repo: https://projects.fse.uni-due.de/svn/alexander-mueller-bloby-warriors/trunk/
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
临界区和互斥体是不好的。它们应该只由库设计者使用,而且通常也不应该使用(因为对于可重用的代码,通常值得付出额外的努力来获得无锁的额外可扩展性)。
相反,您应该使用线程安全队列。 Windows 提供了很多功能:
PostMessage
)只是您的一些选择。
所有这些都经过高度优化,并且比设计自己的队列更容易使用。
Critical sections and mutexes are bad. They should only be used by library designers, and usually not even then (because for reusable code, it's often worth the extra effort to gain the extra scalability of lock-free).
Instead, you should use a threadsafe queue. Windows offers lots:
PostMessage
)are just a few of your options.
All of these are highly optimized and much easier to use than designing your own queue.
我不会再推荐使用 GLUT - 它非常过时且限制性很大。但如果您决定使用它,您可能需要查看 glutIdleFunc 。 GLUT 将在空闲时持续调用此回调 - 您可以使用它在主线程中执行后台处理。
I wouldn't recommend using GLUT anymore - it's terribly outdated and very restrictive. But if you're set on using it, you might want to look into glutIdleFunc. GLUT will continuously invoke this callback when it's idle - you can use this to perform background processing in the main thread.
哦...不,不,不。 线程不是你应该在这里使用的。说真的。在这种特殊情况下,线程不是您的解决方案。让我们回滚一下...
您现在正在使用 GLUT,并且您说您需要线程来“避免锁定
glutMainLoop()
”。并且您不想锁定,因为您想要这样做同时停止一些计算并问自己 - 您确定这些操作需要从 OpenGL 渲染异步(作为一个整体)完成吗?如果是这样,您可以停止阅读这篇文章并查看在其他方面,但我真诚地相信,对于典型的实时 OpenGL 应用程序来说,情况可能并非如此。
所以...典型的 OpenGL 应用程序如下所示:
大多数 GL 窗口库允许。你将其实现为你自己的主循环,GLUT 会用它的“回调”来混淆它,但想法是相同的,
你仍然可以在你的应用程序中引入并行性,但它应该在步骤 2 开始和停止,所以它仍然是顺序的。在主循环级别:“计算一帧计算,然后渲染该帧”。这种方法可能会为您节省很多麻烦。
专业提示:更改您的库。 GLUT 已经过时并且不再维护。切换到 GLFW(或 SDL)来创建窗口不会在代码方面花费太多精力,而且 - 与 GLUT 相反 - 您自己定义主循环,这似乎是您想要在这里实现的目标。 (另外,它们对于输入和窗口事件处理等来说往往更方便)
一些具有恒定时间步长实时物理的典型伪代码,而不干扰渲染(假设您通常希望比渲染更频繁地运行物理) :
一个可能的扩展是使用accum的值作为仅用于渲染的附加“外推”值,这将允许视觉上平滑图形表示,同时很少模拟物理(具有更大的DT) ,可能每个渲染帧不止一次。
Oh... No, no, no. Threads are NOT what you should use here. Seriously. Thread are NOT your solution in this particular case. Let's roll back a bit...
You're using GLUT at the moment and you say you need threads to "avoid locking on
glutMainLoop()
. And you don't want locking because you want to do some calculations in the meantime.Stop now and ask yourself - are you sure that those operations need to be done asynchronically (as a whole) from OpenGL rendering? If so, you may stop reading this post and look at the other ones, but I sincerely believe that it may not be the case for a +- typical real-time OpenGL application.
So... A typical OpenGL app looks like this:
Most GL window libraries let you implement that as your own main loop, GLUT kind of obfuscates that with its "callbacks", but the idea is the same.
You can still introduce parallelism in your application, but it should start and stop at step 2, so it's still sequential on main-loop level: "calculate a frame of calculations, THEN render this frame". This approach is likely to save you a lot of trouble.
Protip: Change your library. GLUT is outdated and not maintained anymore. Switching to GLFW (or SDL) for window creation wouldn't take much effort in terms of code and - contrary to GLUT - you define your main loop yourself, which seems to be what you want to achieve here. (Plus they tend to be more convenient for input & window event handling, etc.)
Some typical pseudocode with constant-timestep real-time physics without interfering with rendering (assuming that you want to run physics more often than rendering, in general):
A possible extension from that is to use the value of
accum
as an additional "extrapolation" value only for rendering, which would allow for visually smooth the graphical representation while simulating physics more seldomly (with bigger DT), possibly more seldomly than once per rendering frame.在主线程中:锁定互斥锁,将包含必要信息的结构/对象添加到某种 FIFO 数据结构中,解锁互斥锁,然后(可选)唤醒后台线程(通过信号或条件变量或写入一个字节到套接字或但是)
在后台线程中:(可选)阻塞直到被主线程唤醒,然后锁定互斥体,从 FIFO 头部弹出第一个项目,解锁互斥体,处理该项目,重复。
In the main thread: lock a mutex, add a struct/object containing the necessary info to a FIFO data structure of some sort, unlock the mutex, then (optionally) wake up the background thread (via a signal or a condition variable or writing a byte to a socket or however)
In the background thread: (optionally) block until awoken by the main thread, then lock the mutex, pop the first item from the head of the FIFO, unlock the mutex, process the item, repeat.