Самый простой мьютекс на свете. Работает ли этот пример? Это потокобезопасно?
Я хотел бы спросить о самом простом Мьютексном подходе для многопоточной обработки. Является ли следующий код потокобезопасным (quick-n-dirty)?
class myclass
{
bool locked;
vector<double> vals;
myclass();
void add(double val);
};
void myclass::add(double val)
{
if(!locked)
{
this->locked = 1;
this->vals.push_back(val);
this->locked = 0;
}
else
{
this->add(val);
}
}
int main()
{
myclass cls;
//start parallelism
cls.add(static_cast<double>(rand()));
}
Работает ли это? Это потокобезопасно? Я просто пытаюсь понять, как можно написать простейший мьютекс.
Если у вас есть какие-либо советы по поводу моего примера, было бы неплохо.Спасибо.
Спасибо, что сказали, что это не работает. Не могли бы вы Предложить исправление, которое не зависит от компилятора?6 ответов:
Это потокобезопасно?
Конечно, нет. Если поток вытеснен между проверкой и установкой блокировки, то второй поток может получить эту блокировку; если контроль затем возвращается к первому потоку, то оба получат его. (И конечно, на современном процессоре два или более ядер могут выполнять одни и те же инструкции одновременно для еще большей веселости.)
По крайней мере, вам нужна операция атомного тестирования и набора, чтобы реализовать такую блокировку. C++11 библиотека предоставляет такую вещь:
std::atomic_flag locked; if (!locked.test_and_set()) { vals.push_back(val); locked.clear(); } else { // I don't know exactly what to do here; // but recursively calling add() is a very bad idea. }
Или еще лучше:
std::mutex mutex; std::lock_guard<std::mutex> lock(mutex); vals.push_back(val);
Если у вас есть более старая реализация, то вам придется полагаться на любые расширения/библиотеки, доступные вам, поскольку в то время в языке или стандартной библиотеке не было ничего полезного.
Нет, это не потокобезопасно. Есть гонка между
if(!locked)
И
this->locked = 1;
Если происходит переключение контекста между этими двумя операторами, ваш механизм блокировки разваливается. Вам нужен атомный
test and set
инструкция, или просто использовать существующийmutex
.
Этот код не обеспечивает атомарную модификацию вектора
vals
. Рассмотрим следующий сценарий://<<< Suppose it's 0 if(!locked) { //<<< Thread 0 passes the check //<<< Context Switch - and Thread 1 is also there because locked is 0 this->locked = 1; //<<< Now it's possible for one thread to be scheduled when another one is in //<<< the middle of modification of the vector this->vals.push_back(val); this->locked = 0; }
Работает ли это? Это потокобезопасно?
Нет. Временами он будет терпеть неудачу.
Ваш мьютекс будет работать только в том случае, если другие потоки никогда не делают ничего между выполнением этих двух строк:
if(!locked) { this->locked = 1;
...и вы не обеспечили этого.
Чтобы узнать о как писать мьютексы, смотрите этот пост SO.
Нет, это не потокобезопасно.
Рассмотрим два потока, выполняющихся более или менее одновременно. Кроме того, представьте, что значение.locked
равноfalse
.Первый поток выполняется до и включая эту строку:
Теперь представьте, что система переключает контекст на второй поток. Он также выполняет до той же строки.if(!locked) {
Теперь у нас есть два разных потока, оба полагая, что они имеют эксклюзивный доступ, и оба внутри
!locked
условия если.Они оба будут звонить
vals.push_back()
более или менее в одно и то же время.Бум.
Другие уже показали, как ваш мьютекс может потерпеть неудачу, поэтому я не буду пересказывать их точки зрения. Я добавлю только одно: самая простая реализация мьютекса намного сложнее, чем ваш код.
Если вас интересует nitty gritty (или даже если это не так - это материал , который должен знать каждый разработчик программного обеспечения ), вы должны посмотреть на алгоритм пекарни Лесли Лэмпорта и пойти оттуда.