Я использую инвертированную блокировку в качестве семафора для оповещения об обновлении очереди (обратите внимание на закомментированный Sleep(1)
, он будет использован позже):
#include <stdio.h>
#include <omp.h>
#include <queue>
#include <stdint.h>
#include <windows.h>
class ThreadLock
{
protected:
omp_lock_t lock;
public:
ThreadLock() {
omp_init_lock(&lock);
}
~ThreadLock() {
omp_destroy_lock(&lock);
}
void acquire() {
omp_set_lock(&lock);
}
void release() {
omp_unset_lock(&lock);
}
};
std::queue< uint32_t > g_queue;
ThreadLock g_lock;
void producer()
{
uint32_t seq = 0;
g_lock.acquire();
while (true) {
Sleep(200);
#pragma omp critical
g_queue.push(++seq);
printf("Produced %u\n", seq);
g_lock.release();
//Sleep(1);
g_lock.acquire();
}
g_lock.release();
}
void consumer()
{
while (true) {
// Lock if empty
if (g_queue.empty()) {
printf("[Consumer] Acquiring lock\n");
g_lock.acquire();
g_lock.release();
printf("[Consumer] Released lock\n");
if (g_queue.empty()) {
printf("Still empty\n");
Sleep(100);
continue;
}
}
#pragma omp critical
{
printf("Consumed %u\n", g_queue.front());
g_queue.pop();
}
}
}
int main(int argc, char* argv[])
{
#pragma omp parallel sections
{
#pragma omp section
consumer();
#pragma omp section
producer();
}
return 0;
}
Этот код содержит условие гонки,который через некоторое время останавливает потребителя следующим образом:
[Consumer] Acquiring lock
Produced 1
Produced 2
[Consumer] Released lock
Consumed 1
Consumed 2
[Consumer] Acquiring lock
Produced 3
Produced 4
Produced 5
Produced 6
Produced 7
Produced 8
Produced 9
Produced 10
Produced 11
Produced 12
Produced 13
Produced 14
Produced 15
Produced 16
Produced 17
Produced 18
Produced 19
Похоже, что поток производителя проходит через освобождение / получение без переключения контекста.Хорошо.Давайте заставим его, раскомментировав Sleep(1)
:
[Consumer] Acquiring lock
Produced 1
[Consumer] Released lock
Consumed 1
[Consumer] Acquiring lock
[Consumer] Released lock
Still empty
[Consumer] Acquiring lock
Produced 2
[Consumer] Released lock
Consumed 2
[Consumer] Acquiring lock
[Consumer] Released lock
Still empty
[Consumer] Acquiring lock
Produced 3
[Consumer] Released lock
Consumed 3
Заметили эти Still empty
строки?Похоже, что покупателю удается вставить дополнительный цикл обработки между линиями выпуска / приобретения производителя.
Я знаю, что добавление еще одного Sleep(1)
в поток потребителя решает проблему.Но я чувствую, что эти фиксированные искусственные задержки в коде неверны (Sleep(200)
не считается, он служит только для демонстрации).
Как это можно сделать правильно, с OpenMP и без версий OpenMPвыше 2,0?