Потоковая безопасность и атомарное чтение в ОС Windows - PullRequest
2 голосов
/ 04 февраля 2011

У меня есть этот кусок кода в моем приложении. Я подозреваю, что это не потокобезопасно, поэтому решите спросить SO.

int * volatile int_ptr;
int count;

Тема 1:

void grow(int new_count)
{
    if(new_count <= count) return;
    int * new_ptr = new int[new_count];
    memset(new_ptr, 0 , new_count * sizeof(int));
    memcpy(new_ptr,int_ptr,count);
    int * dum_ptr = (int *)InterlockedExchange((volatile long *)&int_ptr,(long)new_ptr)     
    count = new_count;
    delete [] dum_ptr;
}

Тема 2:

int get_value(int index)
{
    return int_ptr[index];
}

Я знаю, что можно использовать CRITICAL_SECTION, но поток 1 будет работать, возможно, один раз в неделю, тогда как поток 2 будет работать миллионы раз в день. В 99,9999% попыток доступа к int_ptr 2-й поток будет входить и выходить из критической секции даром. Это просто не имеет смысла для меня. Приложение будет работать только в Windows 2000 и более поздних версиях с процессорами Intel, очевидно, с многоядерными процессорами.

Этот код потокобезопасен? Если нет, что я должен сделать, чтобы сделать его потокобезопасным? Как я могу читать int_ptr атомарно? я. е. :

int * dummy_ptr = read_atomic<int *>(int_ptr);
return dummy_ptr[index];

Решение, включая std::vector, делает меня счастливее и комфортнее.

Ответы [ 3 ]

7 голосов
/ 04 февраля 2011

Нет, это не безопасно. get_value может прочитать значение int_ptr, а затем выйти из расписания. Затем другой поток может поменять int_ptr и delete старое значение. Когда get_value возвращается обратно, он пытается разыменовать значение, прочитанное из int_ptr - которое уже было удалено.

Лучший подход был бы основан на "Read-copy-update" (RCU) , который уже широко использовался в Linux. Основной принцип заключается в том, что вы не удаляете старое значение сразу - вы ждете до некоторого момента времени, когда вы можете быть консервативно уверены , что ничего не имеет старого значения указателя, затем удаляете его. *

К сожалению, в Windows пока нет реализации библиотеки RCU. Вы можете попробовать urcu для Windows, хотя, я полагаю.

2 голосов
/ 04 февраля 2011

Нет, это не так.Один сценарий состоит в том, что thread2 находится внутри функции get_value и выполняет int_ptr[index].Поскольку это не атомарно, это может занять несколько инструкций.В середине этой инструкции происходит переключение контекста потока, и поток1 начинает выполняться с grow.Это будет delete[] int_ptr.Теперь при запуске thread2 он столкнется с нарушением прав доступа.Вы можете использовать CCriticalSection для решения этой проблемы.Поскольку это не объект ядра, производительность в ОС Windows не так уж и плоха.

0 голосов
/ 04 февраля 2011

Рассмотрите возможность создания потока 1, попросите поток 2 выполнить обновление или подождать, пока выполняется обновление.

Чтобы это было полезно, поток 2 должен прослушивать какой-либо сигнал или сообщение от остальныхсистемы.Механизм передачи сообщений или условная переменная могут быть расширены новым сообщением.Также рассмотрим APC (что-то вроде сигналов Unix).

Это должно быть на самом деле запрос, а не принудительная приостановка.Принудительное приостановление потока не решает проблему, так как поток может быть приостановлен в любой момент, в том числе между чтением int_ptr и чтением int_ptr[index].

...