Почему этот мьютекс-код не работает должным образом? - PullRequest
0 голосов
/ 05 марта 2019

В этой теме много сообщений и ответов, но, похоже, ни одна из них не вполне соответствует моей проблеме.После поиска в Google и поиска в стеке, я не вижу ответа на свой вопрос.

У меня есть два потока: главный и подчиненный.Ведомое устройство должно дождаться главного устройства, прежде чем идти дальше определенной точки, поэтому я создал мьютекс в качестве глобальной переменной:

pthread_mutex_t lock;

И затем в инициализации главного потока задолго до того, как ведомое устройствоПоток может получить доступ к нему, я блокирую его:

инициализирую в главном потоке:

pthread_mutex_lock(&lock)

Затем в подчиненном устройстве, когда пришло время ждать мастера, я делаю это:

раб должен ждать здесь:

pthread_mutex_lock(&lock);
pthread_mutex_unlock(&lock);

Между тем, снова в мастере, у меня есть это, когда пришло время "освободить" раба, который заблокирован в ожидании:

pthread_mutex_unlock(&lock);
pthread_mutex_lock(&lock);

(Примечание: порядок блокировки / разблокировки для мастера противоположен.)

Основываясь на моем понимании мьютексов, я подумал, что раб ударит по замку, и застрял там, ожидая мастера, которыйбудет разблокировать его, а затем сразу же заблокировать его снова.С точки зрения времени, ведомому потребуется много времени (гарантировано), чтобы вернуться сюда снова, поэтому у мастера будет много времени, чтобы снова его заблокировать.Точно так же мастер не вернется сюда снова на некоторое время, и нам не нужно беспокоиться о том, что мастер или слэйв будут отбрасывать другого обратно на эти контрольные точки.

Когда он не работает должным образомЯ бросил в некоторых printf's, чтобы подтвердить, что мастер разблокирует, затем повторно блокирует мьютекс, прежде чем ведомый сможет разблокировать его.Насколько я понимаю, подчиненный запросил блокировку задолго до того, как мастер пришел туда, чтобы выполнить разблокировку и (повторную) блокировку, и независимо от того, насколько малое время между разблокировкой и (повторной) блокировкой мастера, подчиненное устройство все еще должно быть в состояниизаблокировать мьютекс, потому что он уже «в очереди» в ожидании.

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

Вот код с принтом в нем и сгенерированным выводом:

slave:

printf("slave thread starting lock sequence\n");fflush(stdout);
pthread_mutex_lock(&lock);
printf("slave thread intra lock sequence\n");fflush(stdout);
pthread_mutex_unlock(&lock);
printf("slave thread completed lock sequence\n");fflush(stdout);

master:

printf("master thread starting lock sequence\n");fflush(stdout);
pthread_mutex_unlock(&lock);
printf("master thread intra lock sequence\n");fflush(stdout);
pthread_mutex_lock(&lock);
printf("master thread completed lock sequence\n");fflush(stdout);

Теперь вот что я вижу как вывод:

последовательность блокировки запуска подчиненного потока

... затем некоторое время (несколько секунд), покаподчиненное устройство блокируется, и, наконец, появляется следующее сообщение:

последовательность блокировки запуска главного потока

последовательность внутреннего блокировки главного потока

основная нить завершенапоследовательность блокировки

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

Так чего же мне не хватает в мьютексах и блокировке / разблокировке??

-gt-

Ответы [ 3 ]

0 голосов
/ 06 марта 2019

Когда это не сработало так, как ожидалось, я бросил в некоторые printf's, чтобы подтвердить, что мастер разблокирует, а затем повторно блокирует мьютекс, прежде чем ведомый сможет разблокировать его.Насколько я понимаю, подчиненный запросил блокировку задолго до того, как мастер пришел туда, чтобы выполнить разблокировку и (повторную) блокировку, и независимо от того, насколько малое время между разблокировкой и (повторной) блокировкой мастера, подчиненное устройство все еще должно быть в состояниизаблокировать мьютекс, потому что он уже «в очереди» в ожидании.

Нет никаких оснований быть справедливыми по отношению к потокам, они не подают жалоб на объединение, если вы плохо обращаетесь с ними.Но есть причина заставить систему в целом выполнять как можно больше работы как можно быстрее.Остановка одного потока для запуска другого оказывает существенное негативное влияние на производительность, так как новый поток запускается со всеми холодными кэшами, и то же самое происходит, когда вы переключаетесь обратно.

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

0 голосов
/ 06 марта 2019

Может быть, вы можете смоделировать проблему после семафора, я обнаружил, что это обычно легче понять и реализовать.что-то вроде следующего примера псевдокода.

//before create slave thread, create semaphore first
sem_t data_ready, slave_done;
sem_init(&data_ready, 0);
sem_init(&slave_done, 0);

В главном потоке:

//master do something AAA
sem_post(data_ready);
//master do something BBB
sem_wait(slave_done);  //master may sleep here, and maybe interrupted by signal, need to handle that
//master do something CCC

В подчиненном потоке:

//slave do something DDD
sem_wait(&data_ready); //slave may sleep here, and maybe interrupted by signal, need to handle that
//slave do something EEE
sem_post(&slave_done);
//slave do something FFF

Возможно, порядок выполнения должен быть AAA[BBB / DDD] EEE [FFF / CCC], это гарантирует, что master-> AAA находится перед slave-> EEE, который работает перед master-> CCC.

0 голосов
/ 06 марта 2019

Как отмечается в комментариях к вашему вопросу, мьютексы pthreads не гарантируют справедливости.

Правильный инструмент для этого задания - переменная общего флага, защищенная мьютексом и ожидающая использования условной переменной:

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
int flag = 0;

Подчиненный ожидает флага:

pthread_mutex_lock(&lock);
while (!flag)
    pthread_cond_wait(&cond, &lock);
pthread_mutex_unlock(&lock);

Когда ведущий хочет освободить подчиненное устройство, он устанавливает флаг и сигнализирует переменную условия:

pthread_mutex_lock(&lock);
flag = 1;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&lock);

(Обратите внимание, что мьютекс не удерживается, пока заблокирован внутри pthread_cond_wait()).

...