Правильно ли использовать механизм ожидания — уведомления с использованием std :: mutex?

Я начал использовать std :: mutexes, чтобы остановить поток и ждать, пока другой поток не возобновит его. Это работает так:

Тема 1

// Ensures the mutex will be locked
while(myWaitMutex.try_lock());
// Locks it again to pause this thread
myWaitMutex.lock();

Тема 2

// Executed when thread 1 should resume processing:
myWaitMutex.unlock();

Однако я не уверен, что это правильно и будет работать без проблем на всех платформах. Если это не правильно, как правильно реализовать это в C ++ 11?

3

Решение

// Ensures the mutex will be locked
while(myWaitMutex.try_lock());

.try_lock() пытается захватить замок и возвращается true в случае успеха, то есть, код говорит «если мы получим замок, попробуйте снова и снова его заблокировать, пока мы не выйдем из строя«. Мы никогда не сможем» потерпеть неудачу «, так как в настоящее время мы сами владеем блокировкой, на которой мы ожидаем, и поэтому это будет бесконечный цикл. Кроме того, попытка блокировки с помощью std::mutex что вызывающий абонент уже установил блокировку на UB, так что это гарантированно UB. Если не удачно, .try_lock() вернусь false и while цикл будет завершен. Другими словами, это будет не убедитесь, что мьютекс будет заблокирован.

Правильный способ обеспечить блокировку мьютекса — это просто:

myWaitMutex.lock();

Это приведет к блокировке текущего потока (на неопределенный срок) до тех пор, пока он не сможет получить блокировку.

Затем другой поток пытается разблокировать мьютекс, который он делает. не иметь блокировку.

// Executed when thread 1 should resume processing:
myWaitMutex.unlock();

Это не будет работать, так как это UB .unlock() на std::mutex что у вас еще нет блокировки.

При использовании мьютекс-блокировок проще использовать объект-оболочку RAII, например std::lock_guard. Модель использования std::mutex всегда: «Заблокировать -> сделать что-то в критическом разделе -> разблокировать«. А std::lock_guard заблокирует мьютекс в своем конструкторе и разблокирует его в деструкторе. Не нужно беспокоиться о том, когда блокировать и разблокировать и тому подобные вещи низкого уровня.

std::mutex m;
{
std::lock_guard<std::mutex> lk{m};
/* We have the lock until we exit scope. */
} // Here 'lk' is destroyed and will release lock.

Если вы хотите, чтобы сигнал проснулся, то есть ждать и уведомить структура с использованием std::condition_variable. std::condition_variable позволяет любому звонящему посылать сигнал ожидающим потокам без удержания замков.

#include <atomic>
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>

using namespace std::literals;

int main() {
std::mutex m;
std::condition_variable cond;

std::thread t{[&] {
std::cout << "Entering sleep..." << std::endl;
std::unique_lock<std::mutex> lk{m};
cond.wait(lk); // Will block until 'cond' is notified.
std::cout << "Thread is awake!" << std::endl;
}};

std::this_thread::sleep_for(3s);

cond.notify_all(); // Notify all waiting threads.

t.join(); // Remember to join thread before exit.
}

Тем не менее, чтобы еще больше усложнить вещи есть эта вещь называется ложные пробуждения это означает, что любые ожидающие потоки могут проснуться в любое время по неизвестным причинам. Это факт в большинстве систем и имеет отношение к внутренней работе планирования потоков. Кроме того, нам, вероятно, нужно проверить, что ожидание действительно необходимо, поскольку мы имеем дело с параллелизмом. Если, например, уведомляющий поток происходит, чтобы уведомить до мы начинаем ждать, тогда мы могли бы ждать вечно если у нас нет способа сначала проверить это.

Чтобы справиться с этим, нам нужно добавить цикл while и предикат, который сообщает, когда нам нужно ждать и когда мы закончим ждать.

int main() {
std::mutex m;
std::condition_variable cond;
bool done = false; // Flag for indicating when done waiting.

std::thread t{[&] {
std::cout << "Entering sleep..." << std::endl;
std::unique_lock<std::mutex> lk{m};
while (!done) { // Wait inside loop to handle spurious wakeups etc.
cond.wait(lk);
}
std::cout << "Thread is awake!" << std::endl;
}};

std::this_thread::sleep_for(3s);

{ // Aquire lock to avoid data race on 'done'.
std::lock_guard<std::mutex> lk{m};
done = true; // Set 'done' to true before notifying.
}
cond.notify_all();

t.join();
}

Существуют и другие причины, по которым стоит подождать внутри цикла и использовать предикат, такой как «украденные пробуждения», как упомянуто в комментариях @Дэвид Шварц.

2

Другие решения

Мне кажется, что вы ищете переменная условия. В конце всегда должен быть способ заставить его работать через мьютексы, но условная переменная является текущим идиоматическим способом C ++ для обработки сценария «блок и ожидание, пока что-то произойдет».

4

Поведение мьютекса, когда поток, который его удерживает, пытается его заблокировать, не определено. Поведение мьютекса, когда поток, который не держит его, пытается разблокировать его, не определено. Так что ваш код может делать что угодно на разных платформах.

Вместо этого используйте мьютекс вместе с условной переменной и логическим предикатом. В псевдокоде:

Блокировать:

  1. Приобретите мьютекс.

  2. Пока предикат равен false, блокируйте переменную условия.

  3. Если вы хотите перезагружать здесь, установите предикат в false.

  4. Отпустите мьютекс.

Выпустить:

  1. Приобретите мьютекс.

  2. Установите для предиката значение true.

  3. Сигнализировать переменную условия.

  4. Отпустите мьютекс.

Перевооружиться:

  1. Приобретите мьютекс.

  2. Установите предикат в ложь.

  3. Отпустите мьютекс.

2

Пожалуйста, проверьте этот код ….

std::mutex m_mutex;
std::condition_variable m_cond_var;

void threadOne(){
std::unique_lock<std::mutex> lck(mtx);
while (!ready){
m_cond_var.wait(lck);
}
m_cond_var.notify_all();
}
void threadTwo(){
std::unique_lock<std::mutex> lck(mtx);
read = true;
m_cond_var.notify_all();
}

Я надеюсь, что вы получите решение. И это очень правильно !!

1
По вопросам рекламы [email protected]