【问题标题】:Inter-thread communication in C++C++中的线程间通信
【发布时间】:2011-05-12 22:44:19
【问题描述】:

我有两个线程(应用程序主线程和另一个)。我正在使用 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() 并且 processor->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/

【问题讨论】:

  • 我该怎么做?我尝试将 Mutex 对象添加到非 OpenGL 线程类,并在关键部分之前和 receiveMessage() 的开头添加了 Lock lock(mutex),但这些部分并不相互排斥。
  • 很难理解您对问题的解释。您能否发布更多代码来准确显示您的 RAII Lock 类的实例化位置?另外,请发布另一个线程(运行 OpenGL 代码的线程)的存根,并显示您的锁插入的位置。

标签: c++ visual-studio multithreading opengl glut


【解决方案1】:

哦...不,不,不。 线程不是你应该在这里使用的。说真的。在这种特殊情况下,线程不是您的解决方案。让我们回滚一点……

您目前正在使用 GLUT,并且您说您需要线程来“避免锁定 glutMainLoop()。而且您不想锁定,因为您想同时进行一些计算。

现在停下来问问自己 - 你确定这些操作需要从 OpenGL 渲染中异步(作为一个整体)完成吗?如果是这样,你可能会停止阅读这篇文章并看看其他的的,但我真诚地相信,对于一个 +- 典型的实时 OpenGL 应用程序来说,情况可能并非如此。

所以...一个典型的 OpenGL 应用程序如下所示:

  • 处理事件
  • 刻度计算
  • 重绘屏幕

大多数 GL 窗口库都允许您将其实现为自己的主循环,GLUT 会通过其“回调”来混淆它,但想法是相同的。

您仍然可以在应用程序中引入并行性,但它应该在第 2 步开始和停止,因此它在主循环级别上仍然是顺序的:“计算一帧计算,然后渲染这一帧”。这种方法可能会为您省去很多麻烦

Protip:改变你的图书馆。 GLUT 已经过时,不再维护。切换到 GLFW(或 SDL)来创建窗口在代码方面不会花费太多精力,而且 - 与 GLUT 相反 - 您自己定义主循环,这似乎是您想要在这里实现的目标。 (此外,它们往往更方便输入和窗口事件处理等)


一些典型的伪代码,具有恒定时间步长的实时物理而不干扰渲染(假设您通常希望运行物理比渲染更频繁):

var accum = 0
const PHYSICS_TIMESTEP = 20
while (runMainLoop) {
    var dt = getTimeFromLastFrame

    accum += dt
    while (accum > PHYSICS_TIMESTEP) {
        accum -= PHYSICS_TIMESTEP
        tickPhysicsSimulation(PHYSICS_TIMESTEP)
    }

    tickAnyOtherLogic(dt)
    render()
}

一个可能的扩展是使用accum 的值作为仅用于渲染的附加“外推”值,这将允许在视觉上平滑图形表示,同时更少模拟物理(具有更大的 DT),可能更多每个渲染帧很少超过一次。

【讨论】:

  • 我以前的版本使用主循环进行计算,但这大大降低了 FPS。我要做的计算是物理计算。我要做的是在另一个线程中渲染物理世界,以便图形与物理解耦。
  • ... 主要问题是物理计算必须在一个必须始终相同的间隔内完成。我正在将整个源代码添加到我的初始帖子中......
  • 将图形和物理解耦当然有用,但它仍然不能从多线程中受益。
  • 我贴的代码不就是这样吗?如果渲染需要很多时间,那么在下一帧中,您仍然可以根据实际需要进行尽可能多的物理模拟迭代和恒定 DT - 此外,您还可以避免额外的同步成本。另请注意,OpenGL 调用实际上是异步的,除非您调用 glFinish(),否则不会阻塞。
  • 我注意到的最明显的事情是在您的 deltaTime 计算中 - this-&gt;previousTicks 应该在计算 deltaTime 之后立即设置为新值,并且您在它们之间进行了许多计算,因此“省略”了很多时间。看看:pastebin.com/fmSvuAFT
【解决方案2】:

在主线程中:锁定互斥体,将包含必要信息的结构/对象添加到某种 FIFO 数据结构中,解锁互斥体,然后(可选)唤醒后台线程(通过信号或条件变量或将字节写入套接字或但是)

在后台线程中:(可选)阻塞直到被主线程唤醒,然后锁定互斥体,从 FIFO 头部弹出第一个项目,解锁互斥体,处理项目,重复。

【讨论】:

    【解决方案3】:

    关键部分和互斥锁很糟糕。它们应该只被库设计者使用,而且通常情况下也不应该使用(因为对于可重用的代码,通常值得付出额外的努力来获得无锁的额外可伸缩性)。

    相反,您应该使用线程安全队列。 Windows 提供很多:

    • 线程消息队列(PostMessage)
    • 邮槽
    • 消息模式管道
    • 数据报套接字
    • SList API

    只是您的一些选择。

    所有这些都经过高度优化,比设计自己的队列更容易使用。

    【讨论】:

    • 我认为你的第一段有点疯狂。也许不适用于最高级别的代码,当然也不适用于未经培训的开发人员,但互斥锁对于许多类型的开发来说都是生活中的事实。
    • “关键部分和互斥锁很糟糕。”我完全不同意。
    • @up - 我想这种方法(听起来确实很有趣)与称为“无锁”的 MT 范式有关。
    • @John:取决于您将它们与什么进行比较。他们坏的。只是有时,不存在不坏的选择。 ;) 它们是实现同步的糟糕方法。不可组合且容易出错,有什么不喜欢它们的? ;) 我不认为@Ben 意味着尝试实现并发没有同步是首选。
    • @jalf:这么说吧,我明白你的意思了。是的,我现在同意这一点。
    【解决方案4】:

    我不会再推荐使用 GLUT - 它非常过时并且非常严格。但如果您打算使用它,您可能需要查看glutIdleFunc。 GLUT 在空闲时会不断调用此回调 - 您可以使用它在主线程中执行后台处理。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-01-05
      相关资源
      最近更新 更多