Что произойдет, если два потока читают и записывают один и тот же фрагмент памяти

Насколько я понимаю, если два потока читают из одного и того же фрагмента памяти, и ни один поток не записывает в эту память, тогда операция безопасна. Однако я не уверен, что произойдет, если один поток читает, а другой пишет. Что случилось бы? Результат не определен? Или прочитанное будет просто устаревшим? Если устаревшее чтение не вызывает беспокойства, можно ли иметь несинхронизированное чтение-запись для переменной? Или возможно, что данные будут повреждены, и ни чтение, ни запись не будут правильными, и в этом случае всегда следует синхронизировать?

Я хочу сказать, что я узнал, что это более поздний случай, когда гонка при доступе к памяти состояние остается неопределенным ... но я не помню, где я мог это узнать, и мне трудно найти ответ в Google. Моя интуиция заключается в том, что переменная обрабатывается в регистрах, и что истинный (как в аппаратном) параллелизм невозможен (или это так), поэтому худшее, что может произойти, - это устаревшие данные, то есть следующее:

WriteThread: copy value from memory to register
WriteThread: update value in register
ReadThread:  copy value of memory to register
WriteThread: write new value to memory

В какой момент поток чтения имеет устаревшие данные.

7
задан cheshirekow 26 August 2010 в 23:20
поделиться

3 ответа

Результат не определен. Поврежденные данные вполне возможны. В качестве очевидного примера рассмотрим 64-битное значение, которым управляет 32-битный процессор. Предположим, что значение представляет собой простой счетчик, и мы увеличиваем его, когда младшие 32 бита содержат 0xffffffff. Приращение дает 0x00000000. Когда мы обнаруживаем это, мы увеличиваем старшее слово. Если, однако, какой-то другой поток считывает значение между моментом увеличения младшего слова и увеличением старшего слова, они получают значение с неувеличенным старшим словом, но младшее слово установлено в 0 — значение совершенно другое. от того, что было бы до или после завершения приращения.

9
ответ дан 6 December 2019 в 09:57
поделиться

Обычно память считывается или записывается в атомарных единицах, определяемых архитектурой ЦП (в наши дни 32-битные и 64-битные элементы выровнены по 32-битным и 64-битным границам).

В этом случае происходящее зависит от объема записываемых данных.

Давайте рассмотрим случай 32-битных атомарных ячеек чтения/записи.

Если два потока записывают 32 бита в такую ​​выровненную ячейку, то абсолютно точно определено, что происходит: одно из двух записанных значений сохраняется. К сожалению для вас (ну и программы), вы не знаете какое значение. Чрезвычайно умное программирование позволяет использовать атомарность операций чтения и записи для построения алгоритмов синхронизации (например, алгоритм Деккера), но вместо этого обычно быстрее использовать архитектурно определенные блокировки.

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

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

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

10
ответ дан 6 December 2019 в 09:57
поделиться

Как я намекнул в ответе Иры Бакстер, кэш процессора также играет роль в многоядерных системах. Рассмотрим следующий тестовый код:

ОПАСНО БУДЕТ РОБИСОН!

Следующий код увеличивает приоритет в реальном времени для достижения несколько более стабильных результатов — хотя для этого требуются права администратора, будьте осторожны при запуске кода на двух- или одноядерных системах, так как ваша машина будет заблокирована на время работы. тестовый забег.

#include <windows.h>
#include <stdio.h>

const int RUNFOR = 5000;
volatile bool terminating = false;
volatile int value;

static DWORD WINAPI CountErrors(LPVOID parm)
{
    int errors = 0;
    while(!terminating)
    {
        value = (int) parm;
        if(value != (int) parm)
            errors++;
    }
    printf("\tThread %08X: %d errors\n", parm, errors);
    return 0;
}

static void RunTest(int affinity1, int affinity2)
{
    terminating = false;
    DWORD dummy;
    HANDLE t1 = CreateThread(0, 0, CountErrors, (void*)0x1000, CREATE_SUSPENDED, &dummy);
    HANDLE t2 = CreateThread(0, 0, CountErrors, (void*)0x2000, CREATE_SUSPENDED, &dummy);

    SetThreadAffinityMask(t1, affinity1);
    SetThreadAffinityMask(t2, affinity2);
    ResumeThread(t1);
    ResumeThread(t2);

    printf("Running test for %d milliseconds with affinity %d and %d\n", RUNFOR, affinity1, affinity2);
    Sleep(RUNFOR);
    terminating = true;
    Sleep(100); // let threads have a chance of picking up the "terminating" flag.
}

int main()
{
    SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS);
    RunTest(1, 2);      // core 1 & 2
    RunTest(1, 4);      // core 1 & 3
    RunTest(4, 8);      // core 3 & 4
    RunTest(1, 8);      // core 1 & 4
}

В моей системе с четырехъядерным процессором Intel Q6600 (у iirc есть два набора ядер, каждый из которых использует общий кэш L2 — в любом случае это объясняет результаты ;)), я получаю следующие результаты:

Running test for 5000 milliseconds with affinity 1 and 2
        Thread 00002000: 351883 errors
        Thread 00001000: 343523 errors
Running test for 5000 milliseconds with affinity 1 and 4
        Thread 00001000: 48073 errors
        Thread 00002000: 59813 errors
Running test for 5000 milliseconds with affinity 4 and 8
        Thread 00002000: 337199 errors
        Thread 00001000: 335467 errors
Running test for 5000 milliseconds with affinity 1 and 8
        Thread 00001000: 55736 errors
        Thread 00002000: 72441 errors
0
ответ дан 6 December 2019 в 09:57
поделиться
Другие вопросы по тегам:

Похожие вопросы: