Проблемы:
Вообще есть две основные проблемы с параллельным кодом.
<сильный>1. Атомарность
Наименьшая степень детализации кода — это на самом деле не отдельные операции, такие как i++
, а лежащие в их основе ассемблерные инструкции. Поэтому каждая операция, включающая запись, не может быть вызвана из нескольких потоков. (это сильно зависит от вашей целевой архитектуры, но x86, в отличие от arm64, очень ограничен)
Но, к счастью, c++ предоставляет операции std::atomic
, которые дают вам хороший независимый от платформы способ изменения переменных из нескольких потоков.
<сильный>2. Согласованность
И компилятору, и процессору разрешено переупорядочивать любые инструкции до тех пор, пока сохраняется согласованность локального потока. Итак, что это значит?
посмотри на свою первую тему
if( c = 0 )
{
c++;
x++; // value that is incremented
}
У вас есть 3 операции c == 0
, c++
и x++
. Оба приращения не зависят друг от друга, поэтому компилятору будет разрешено менять их местами. Во время выполнения ядро также может изменить их порядок, оставив вас в очень неопределенной ситуации. В последовательном мире это совершенно нормально и улучшает общую производительность (если только это не приводит к дырам в безопасности, таким как расплавление). К сожалению, ни компилятор, ни процессор не распознают параллельный код, поэтому любая оптимизация может сломать вашу параллельную программу.
Но опять же, C++ предоставляет встроенное решение этой проблемы, называемое std::memory_order
, которое реализует конкретную модель согласованности.
Решения:
Простой мьютекс. Мьютекс — это простой, но мощный инструмент. Он решает проблемы с атомарностью и согласованностью, предоставляя так называемые критические секции, которые предотвращают параллельное выполнение. Это означает, что в данном примере оператор if в обоих потоках является последовательным и никогда не будет выполняться параллельно. Реализация работает, но есть недостаток. Если один из потоков работает очень медленно, другой будет тратить много процессорного времени на непрерывную проверку флага newValue
.
#include <mutex>
std::mutex mutex;
int value = true;
bool newValue = false;
void producer_thread() {
while(true) {
std::lock_guard<std::mutex> lg(mutex);
if (newValue == false) {
value++;
newValue = true;
}
}
}
void consumer_thread() {
while(true) {
std::lock_guard<std::mutex> lg(mutex);
if (newValue == true) {
std::cout << value;
newValue = false;
}
}
}
Переменная условия:
Условная переменная — это, по сути, просто конструкция «ожидание уведомления». Вы можете заблокировать текущее выполнение, вызвав wait
, пока другой поток не вызовет notify
. Эта реализация будет сценарием перехода.
#include <mutex>
#include <condition_variable>
std::mutex mutex;
std::condition_variable cond;
int value = true;
bool newValue = false;
void producer() {
while(true) {
std::unique_lock<std::mutex> ul(mutex);
while (newValue == true) {
cond.wait(ul);
}
value++;
newValue = true;
cond.notify_all();
}
}
void consumer() {
while(true) {
std::unique_lock<std::mutex> ul(mutex);
while (newValue == false) {
cond.wait(ul);
}
std::cout << value;
newValue = false;
cond.notify_all();
}
}
person
Domso
schedule
14.02.2018