Как избежать использования volatile для программы без блокировки? - PullRequest
0 голосов
/ 20 октября 2019

Моя программа состоит из нескольких потоков, счастливо идущих друг за другом, и единственная синхронизация, которую я имею, - это volatile global bool, которая сообщает им, вышел ли пользователь. Все остальные коммуникации между потоками свободны от блокировки. Этим потокам постоянно приходится работать в критически важных приложениях, поэтому я не могу позволить себе иметь блокировки между ними. Недавно я обнаружил много информации, показывающей, что volatile вреден для многопоточности, поэтому я хочу улучшить свой код. Я видел, что std::atomic_flag гарантированно свободен от блокировок, но я не могу понять, как использовать его для моего случая.

Базовая настройка такая (без файлов, в которых находится код):

// Global variable in its own .h file
extern volatile bool SystemOnline;

// Initialization in a .cpp file    
volatile bool SystemOnline = true;

// Windows message processing
while (SystemOnline)
{
    MSG msg;
    while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
    {
        if (msg.message == WM_QUIT)
        {
            SystemOnline = false;
        }
        else if (!TranslateAccelerator(msg.hwnd, NULL, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
}

// Thread functions
void Thread1()
{
    while (SystemOnline)
    {
        // Do some work
    }
}

void Thread2()
{
    while (SystemOnline)
    {
        // Do some other work
    }
}

// And so on...

Все потоки объединяются в конце.

1 Ответ

1 голос
/ 20 октября 2019

Во-первых, давайте сначала исправим проблему с большей производительностью. Ваш основной поток Win32 крутится без ожидания. Это сведет на нет любое воспринимаемое различие в производительности между bool без блокировки и std :: atomic.

Вы сожжете все ядро, просто вызывая PeekMessage с избыточностью в пустой очереди. Поэтому вместо этого:

while (SystemOnline)
{
    MSG msg;
    while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
    {
        if (msg.message == WM_QUIT)
        {
            SystemOnline = false;
        }
        else if (!TranslateAccelerator(msg.hwnd, NULL, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
}

Давайте сделаем следующее, которое в точности эквивалентно тому, что у вас есть выше, за исключением того, что GetMessage будет блокироваться, пока не поступит входящее сообщение. И GetMessage возвращает FALSE при удалении сообщения WM_QUIT.

MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
    if (!TranslateAccelerator(msg.hwnd, NULL, &msg))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}
SystemOnline = false;

И, как уже отмечали другие, преобразование из глобального bool в атомарное довольно просто:

Просто измените этот глобальный:

// Global variable in its own .h file
extern volatile bool SystemOnline;

// Initialization in a .cpp file    
volatile bool SystemOnline = true;

Для этого:

// Global variable in its own .h file
#include <atomic>
extern std::atomic<bool> SystemOnline;

// Initialization in a .cpp file    
std::atomic<bool> SystemOnline(true);

Вы также можете использовать std::atomic_bool - это typedef для std::atomic<bool>

И это все, что вам нужно сделать,Вам даже не нужно менять код потока, поскольку оператор () для этого типа просто вызывает метод load() для объекта. Точно так же присваивание = false в конце цикла основного потока аналогично вызову SystemOnline.store(false).

Как отмечали другие, на самом деле не так уж много потери производительности для использования атомарного,На x86 он сопоставляется с префиксом кода операции LOCK .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...