Межпотоковое общение в C ++ - PullRequest
3 голосов
/ 20 ноября 2010

У меня есть два потока (основной поток приложений и еще один).Я использую OpenGL, чтобы рисовать некоторые вещи, и я использую обратные вызовы клавиатуры и мыши OpenGL.OpenGL блокируется, когда я вызываю glutMainLoop () и, поскольку мне приходится делать некоторые вычисления в фоновом режиме, я создал другой поток.Теперь обратные вызовы 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);
}

Так что, если receiveMessage () вызывается из потока OpenGL и процессор-> 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 МБ)

РЕДАКТИРОВАТЬ 2:

А вот репозиторий SVN: https://projects.fse.uni -due.de / СВН / александр-Mueller-bloby-воины / багажник /

Ответы [ 4 ]

4 голосов
/ 20 ноября 2010

Ох ... Нет, нет, нет. Темы не то, что вы должны использовать здесь. Серьезно. Нить не ваше решение в данном конкретном случае. Давайте немного откатимся ...

В данный момент вы используете GLUT и говорите, что вам нужны потоки, чтобы «избежать блокировки на glutMainLoop()». И вам не нужна блокировка, потому что вы хотите сделать некоторые вычисления в то же время.

Остановитесь сейчас и спросите себя - Вы уверены, что эти операции должны выполняться асинхронно (в целом) при рендеринге OpenGL? Если это так, вы можете перестать читать этот пост и посмотреть другие , но я искренне верю, что это не может иметь место для + - типичного приложения OpenGL реального времени.

Итак ... Типичное приложение OpenGL выглядит так:

  • обработка событий
  • тиковые расчеты
  • перерисовать экран

Большинство оконных библиотек GL позволяют вам реализовать это как свой собственный главный цикл, GLUT как бы скрывает это своими «обратными вызовами», но идея та же.

Вы все еще можете ввести параллелизм в своем приложении, но он должен начинаться и останавливаться на шаге 2, поэтому он все еще последовательн на уровне основного цикла: «вычислить кадр вычислений, ТОТ отрендерить этот кадр». Такой подход, скорее всего, избавит вас от многих неприятностей .

Подсказка: измени свою библиотеку. 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), возможно, более редко, чем один раз за кадр рендеринга.

1 голос
/ 20 ноября 2010

Я бы не рекомендовал больше использовать GLUT - он ужасно устарел и очень ограничен.Но если вы настроены на его использование, вы можете посмотреть glutIdleFunc .GLUT будет постоянно вызывать этот обратный вызов, когда он не используется - вы можете использовать его для фоновой обработки в главном потоке.

1 голос
/ 20 ноября 2010

Критические секции и мьютексы плохие.Они должны использоваться только разработчиками библиотек, и, как правило, даже тогда (потому что для многократно используемого кода часто требуются дополнительные усилия для обеспечения дополнительной масштабируемости без блокировок).

Вместо этого вы должны использовать потокобезопасныйочередь.Windows предлагает лоты:

  • очередь сообщений потока (PostMessage)
  • почтовых ящиков
  • каналы в режиме сообщений
  • сокеты датаграмм
  • SList API

- это лишь некоторые из ваших вариантов.

Все они высоко оптимизированы и намного проще в использовании, чем создание собственной очереди.

1 голос
/ 20 ноября 2010

В основном потоке: заблокировать мьютекс, добавить структуру / объект, содержащий необходимую информацию, в какую-либо структуру данных FIFO, разблокировать мьютекс, а затем (необязательно) активировать фоновый поток (с помощью сигнала или условия). переменная или запись байта в сокет или как бы то ни было)

В фоновом потоке: (необязательно) блокировать до пробуждения основным потоком, затем заблокировать мьютекс, вытолкнуть первый элемент из головы FIFO, разблокировать мьютекс, обработать элемент, повторить.

...