#c #multithreading #deadlock
#c #многопоточность #взаимоблокировка
Вопрос:
Я использую два потока для обновления глобальной переменной. Чтобы добиться взаимного исключения, я использую эту test_and_set
функцию. Но этот код переходит в взаимоблокировку в какой-то случайный момент во время выполнения. Пожалуйста, помогите.
#include <stdio.h>
#include <pthread.h>
#include <stdatomic.h>
int a = 0;
atomic_int lock = 0;
int test_and_set(int *lock)
{
int l = *lock;
*lock = 1;
return l;
}
void *func(void * param)
{
for(int i = 0; i < 100000; i )
{
while(test_and_set(amp;lock));
a ;
lock = 0;
}
}
int main()
{
pthread_t p1, p2;
pthread_create(amp;p1, NULL, func);
pthread_create(amp;p2, NULL, func);
pthread_join(p1, NULL);
pthread_join(p2, NULL);
printf("%d, %dn", a, b);
return 0;
}
Ответ №1:
Да, рано или поздно это приведет к взаимоблокировке.
Вот как:
-
Поток A «получит блокировку» и будет готов к выполнению
a
иlock = 0;
-
Поток B берет верх и выполняет
l = *lock;
. Поскольку A имеет блокировкуl
, теперь 1. -
Поток A берет верх и выполняет оба
a
иlock = 0;
. Это устанавливает блокировку на ноль. -
Поток B принимает управление, выполняет
*lock = 1;
и возвращаетl
значение 1.
Теперь это взаимоблокировка. Значение lock
равно 1. Поток возвращает 1 из test_and_set
и останется в while
. Поток B начнет считывать блокировку, но всегда будет получать 1. Нет потока, который установит блокировку на ноль. Взрыв…
Аналогичным образом вы можете создать ситуацию, когда оба потока получают «блокировку» одновременно.
Короче говоря: вы не можете реализовать подобную блокировку.
Из https://en.wikipedia.org/wiki/Test-and-set:
В информатике инструкция test-and-set — это инструкция, используемая для записи 1 (set) в ячейку памяти и возврата ее старого значения в виде одной атомарной (т. Е. Не прерываемой) операции.
У вас нет «одной атомной операции». Поэтому поток A выше может выполнить шаг 3 между потоком B, выполняющим шаг 2 и шаг 4.
Если ваш процессор имеет инструкцию test-and-set, вы можете попробовать использовать код на ассемблере.
Примечание: в «реальном» мире переключение задач не происходит в соответствии с операторами C. Один оператор C обычно реализуется как несколько машинных инструкций. Переключение задачи может произойти в любой машинной инструкции. Принцип тот же.
Ответ №2:
Я еще не понял, где происходит ваша взаимоблокировка, но это неправильный способ реализовать блокировку данных, совместно используемых потоками. Предположим, что lock
это 0 и что оба потока вызываются test_and_set
одновременно. Они оба увидят, что lock
это 0, и оба установят его равным 1. Возвращается значение 0, и поэтому оба потока думают, что у них есть блокировка.
Мьютекс (сокращение от «взаимного исключения») выполняет то, что вы пытаетесь сделать здесь, гарантируя, что вышеупомянутый асинхронный беспорядок не может произойти.
Когда вы вызываете pthread_mutex_lock
мьютекс, эта функция не вернется, пока вызывающий поток не получит блокировку. Это означает, что любой другой вызывающий поток pthread_mutex_lock
будет блокироваться до тех пор, пока первый поток не освободит мьютекс путем вызова pthread_mutex_unlock
.
Вот как вы должны использовать мьютекс.
int a = 0;
pthread_mutex_t lock;
void *func(void * param)
{
for(int i = 0; i < 100000; i )
{
pthread_mutex_lock(amp;lock):
a ;
pthread_mutex_unlock(amp;lock);
}
}
int main() {
pthread_t p1, p2;
pthread_mutex_init(amp;lock,NULL);
...
}
Комментарии:
1. Спасибо за объяснение, но я хочу синхронизировать без мьютекса (это вопрос интервью). Не могли бы вы объяснить, как реализован мьютекс?
2. Я считаю, что мьютексы реализованы в ядре. Я не знаю механики.
3. Вы могли бы посмотреть на блокировки вращения .
4. Похоже, у вас почти реализована блокировка вращения в вашем исходном сообщении. Однако вам нужно, чтобы первые две строки
test_and_set
выполнялись атомарно.5. Посмотрите на
atomic_exchange
fromsdatomic.h
.