Должен ли я использовать критический раздел или барьер памяти при настройке общей переменной? - PullRequest
0 голосов
/ 20 мая 2018

Скажем, у меня есть следующий код:

/* Global Variables */

int flag = 0;
int number1;
int number2;

//------------------------------------

/* Thread A */

number1 = 12345;
number2 = 678910;
flag = 1;

//------------------------------------

/* Thread B */

while (flag == 0) {}
printf("%d", number1);
printf("%d", number2);

В Потоке A , код может выполняться не по порядку, он может быть выполнен, например, так:

/* Thread A */

flag = 1;
number1 = 12345;
number2 = 678910;

Чтобы предотвратить это, я должен использовать барьер памяти.

Но я не уверен, должен ли я использовать обычный барьер памяти, например:

/* Thread A */

number1 = 12345;
number2 = 678910;
MEMORY_BARRIER_GOES_HERE
flag = 1;

Илиесли я должен использовать критический раздел, например:

/* Thread A */

number1 = 12345;
number2 = 678910;
EnterCriticalSection(&cs);
flag = 1;
LeaveCriticalSection(&cs);

Ответы [ 3 ]

0 голосов
/ 20 мая 2018

в вашем конкретном примере вам нужно ровно Порядок выпуска-получения

int number1, number2, flag = 0;

/* Thread A */

number1 = 12345;
number2 = 678910;
//--------------
atomic_store_explicit(&flag, 1, memory_order_release);


/* Thread B */
if (atomic_load_explicit(&flag, memory_order_acquire) != 0)
{
    //--------------
    printf("%d", number1);
    printf("%d", number2);
}

Если элементарное хранилище в потоке A помечен memory_order_release и атомная загрузка в потоке B из той же переменной (flag) помечена memory_order_acquire, все записи в память (неатомарные и расслабленные атомарные), которые произошли доатомарные хранилища с точки зрения потока A, становятся видимыми побочными эффектами в потоке B, то есть после завершения атомарной загрузки поток B гарантированно видит все (number1, number2) потока A, записанные в память.

также вы можете определить флаг как volatile int flag и использовать /volatile:ms CL.exe параметр:

int number1, number2;
volatile int flag = 0;

/* Thread A */

number1 = 12345;
number2 = 678910;
//--------------
flag = 1;


/* Thread B */
if (flag)
{
    //--------------
    printf("%d", number1);
    printf("%d", number2);
}

/ volatile: мс

Выбирает расширенную семантику Microsoft, которая добавляет гарантии упорядочения памяти, помимо языка C ++ стандарта ISO.Семантика получения / выпуска гарантируется при нестабильном доступе.Однако эта опция также заставляет компилятор генерировать аппаратные барьеры памяти, которые могут добавить значительные накладные расходы на ARM и другие слабые архитектуры упорядочения памяти.Если компилятор нацелен на любую платформу, кроме ARM, это интерпретация volatile по умолчанию.

, но в любом случае while (flag == 0) ; не является хорошим решением (спин-блокировка).здесь можно использовать set / wait on event, условные переменные, отправлять / отправлять сообщения в конкретный поток или iocp.зависит от конкретной задачи

0 голосов
/ 22 мая 2018

Если вы объявите свой флаг как ДЛИТЕЛЬНЫЙ, тогда вы можете сделать:

InterlockedExchange (&flag, 1);

Это создает полный барьер памяти, см. MSDN .Учитывая, что это помечено как вопрос C. Это, кажется, хороший способ сделать это.

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

0 голосов
/ 20 мая 2018

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

Вам необходимо защитить все переменные (number1, number2 и flag) с помощью блокировки (критический раздел)

Тема A:

EnterCriticalSection(&cs);
    flag = 1;
    number1 = 12345;
    number2 = 678910;
LeaveCriticalSection(&cs);

Тема B:

while (1)
{
    int n1, n2;

    EnterCriticalSection(&cs);
    if (flag)
    {
        n1 = number1;
        n2 = number2;
        break;
    }
    LeaveCriticalSection(&cs);
}
printf("%d", n1);
printf("%d", n2);

Кроме того, в Windows вы можете избежать всего цикла while (flag == 0) {} от записи ядра ЦП с помощью условной переменной .Переключение с механизма непрерывного опроса на механизм, основанный на уведомлениях, даст гораздо лучшие результаты производительности, чем попытки выполнить сложные операции с блокировкой.

Лучше:

Поток A:

EnterCriticalSection(&cs);
    flag = 1;
    number1 = 12345;
    number2 = 678910;
LeaveCriticalSection(&cs);
WakeAllConditionVariable(&conditional_variable);

Резьба B:

EnterCriticalSection(&cs);

while (flag == 0)
{
    // This will atomically Leave the CS and block until the conditional_variable is fired by the other thread

    SleepConditionVariableCS(&conditional_variable, &cs, INFINITE);

    // After it returns, it will re-enter the CS.
}

n1 = number1;
n2 = number2;
LeaveCriticalSection(&cs);
printf("%d", n1);
printf("%d", n2);   
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...