Сразу прошу извинить меня, если баян, поиск почему-то отказался мне помочь.
Я вообще редко пишу под *nix, но когда приходится, меня вообще часто радуют странным поведением библиотечные функции. Этот раз тоже не стал исключением
В моей программе есть несколько потоков, один из них работает с какими-то данными, второй иногда (редко) пытается эти данные модифицировать. Для разграничения доступа я навесил на данные мутекс. Однако, получилось, что второй поток вообще не может получить доступа к данным. Я попытался воспроизвести ситуацию в тестовом примере и вот что у меня получилось:
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void* threadfunc(void * str)
{
printf("starting thread %s", str);
while (true)
{
pthread_mutex_lock(&mutex);
printf((char *)str);
sleep(2);
pthread_mutex_unlock(&mutex);
}
return 0;
}
int main()
{
pthread_t thread1, thread2;
pthread_create(&thread1, 0, threadfunc, (void*)"thread1\n");
sleep(1);
pthread_create(&thread2, 0, threadfunc, (void*)"thread2\n");
sleep(100);
// здесь должна быть остановка потоков, но непринципиальноreturn 0;
}
Эта программа ни разу не даёт потоку thread2 захватить мутекс. Я понимаю, что нельзя ожидать того, что они будут захватывать мутекс по очереди, но я ведь вправе рассчитывать, что каждый поток в принципе получит доступ к нему? Если нет — то вообще на кой такой примитив синхронизации нужен?
Это лыжи не едут или я ...?
PS проверял на
Linux system 2.6.16-2-xen-amd64-k8 #1 SMP Sun Jul 16 03:25:55 CEST 2006 x86_64 GNU/Linux
Linux system 2.6.9-5.EL #1 Wed Jan 5 19:22:18 EST 2005 i686 i686 i386 GNU/Linux
Получается что lock происходит сразу же после unlock, и другой поток просто не успевает ничего сделать.
Поставьте sleep(1) после unlock и они будут по очеред захватывать мьютекс.
Re[2]: pthread mutex concurrent threads
От:
Аноним
Дата:
25.10.07 10:44
Оценка:
Здравствуйте, Аноним, Вы писали:
кстати, вопрос насколько данный пример корректен,
спать имея блокировку довольно странно, а если убрать "sleep",
то все работает,
кстати а почему просто не повысить приоритет потоку который никак не может захватить mutex,
в данном примере это не понятно какой (у меня управление получал и первый и второй),
но судя по описанию есть какой-то один который так и не может захватить мьютекс.
C>Получается что lock происходит сразу же после unlock, и другой поток просто не успевает ничего сделать. C>Поставьте sleep(1) после unlock и они будут по очеред захватывать мьютекс.
здесь все немного хитрее на мой взгляд,
даже если один поток не успевает, то все равно кванты времени отпущенные первому должны истечь,
и управление на некоторое время получит другой,
но т.к. во время sleep кванты времени не тратяться, то субьективно времени прошло много и кванты не истекли,
а объективно у потока еще осталось время.
MD>В моей программе есть несколько потоков, один из них работает с какими-то данными, второй иногда (редко) пытается эти данные модифицировать. Для разграничения доступа я навесил на данные мутекс. Однако, получилось, что второй поток вообще не может получить доступа к данным. Я попытался воспроизвести ситуацию в тестовом примере и вот что у меня получилось:
Прикольно, да. Явная бага в реализации мутексов.
Однако бага проявляется только если один из потоков держит мутекс практически 100% времени. Это явный дефект дизайна программы, так быть не должно.
Здравствуйте, Pzz, Вы писали:
MD>>В моей программе есть несколько потоков, один из них работает с какими-то данными, второй иногда (редко) пытается эти данные модифицировать. Для разграничения доступа я навесил на данные мутекс. Однако, получилось, что второй поток вообще не может получить доступа к данным. Я попытался воспроизвести ситуацию в тестовом примере и вот что у меня получилось:
Pzz>Явная бага в реализации мутексов.
Почему бага?
Вот например в таком случае работают оба потока:
static volatile int m;
void* threadfunc(void * str)
{
int i;
printf("starting thread %s", (char *)str);
while (1)
{
pthread_mutex_lock(&mutex);
printf((char *)str);
// sleep(2);for (i = 0; i < 1000; ++i)
++m;
pthread_mutex_unlock(&mutex);
}
return 0;
}
Pzz>>Явная бага в реализации мутексов.
А>Почему бага?
Потому, что и в том случае должны были бы работать оба потока. Попытка захвата мутекса должна иметь как минимум семантику вставания в очередь желающих захватить мутекс (прямо в POSIX'е этого требования нет, но оно следует из здравого смысла). Как максимум, должна еще учитываться проблема инверсии приоритетов — это когда более приоритетный поток вынужден слишком долго ждать менее приоритетный, которому посчастливилось захватить мутекс первым.
Здравствуйте, Pzz, Вы писали:
А>>Почему бага? Pzz>Потому, что и в том случае должны были бы работать оба потока.
Если обеспечиваемая потоками функциональность идентична, то ИМХО с точки зрения затрачиваемых ресурсов дешевле отпускать один и тот же поток. А вот решение об идентичности функциональности должен принимать программист и с учетом этого выбирать методы синхронизации.
Обязательно бахнем! И не раз. Весь мир в труху! Но потом. (ДМБ)
ДД>Если обеспечиваемая потоками функциональность идентична, то ИМХО с точки зрения затрачиваемых ресурсов дешевле отпускать один и тот же поток. А вот решение об идентичности функциональности должен принимать программист и с учетом этого выбирать методы синхронизации.
И какие существуют в природе методы синхронизации, обеспечивающие семантику мутекса, и при этом гарантирующие, что потоки будут исполняться по-очереди, а не "кто первый встал, того и тапки"?
Pzz>И какие существуют в природе методы синхронизации, обеспечивающие семантику мутекса, и при этом гарантирующие, что потоки будут исполняться по-очереди, а не "кто первый встал, того и тапки"?
Ну на самом деле, я думаю, можно придумать как, комбинируя мутексы, добиться "честного" шедулинга. Другой вопрос в принципе — на кой нам ОС если честный шедулер приходится писать руками?
Здравствуйте, citrin, Вы писали:
C>Получается что lock происходит сразу же после unlock, и другой поток просто не успевает ничего сделать. C>Поставьте sleep(1) после unlock и они будут по очеред захватывать мьютекс.
это поможет починить конкретный пример. проблема же в том, что так ведь не должно быть, должно всё нормально и без sleep работать, так? или я что-то не понимаю?
А>кстати а почему просто не повысить приоритет потоку который никак не может захватить mutex, А>в данном примере это не понятно какой (у меня управление получал и первый и второй), А>но судя по описанию есть какой-то один который так и не может захватить мьютекс.
А позвольте поинтересоваться Вашей конфигурацией
У меня на ядрах 2.6 управление получает только тот поток, который первым захватил мутекс. На ядрах 2.4 всё работает, как Вы говорите
Здравствуйте, Pzz, Вы писали:
Pzz>И какие существуют в природе методы синхронизации, обеспечивающие семантику мутекса, и при этом гарантирующие, что потоки будут исполняться по-очереди, а не "кто первый встал, того и тапки"?
От ситуации зависит. В случае как у топикстартера, когда есть один непрерывно работающий поток, и второй — периодически модифицирующий данные, я бы использовал условную переменную для сигнализации.
Обязательно бахнем! И не раз. Весь мир в труху! Но потом. (ДМБ)
Здравствуйте, green.nsk, Вы писали:
Pzz>>И какие существуют в природе методы синхронизации, обеспечивающие семантику мутекса, и при этом гарантирующие, что потоки будут исполняться по-очереди, а не "кто первый встал, того и тапки"?
GN>Ну на самом деле, я думаю, можно придумать как, комбинируя мутексы, добиться "честного" шедулинга.
Эээ. Ну да, наверное из 2-х линуксячих мутексов можно сделать один нормальный, организовав очередь вручную.
GN>Другой вопрос в принципе — на кой нам ОС если честный шедулер приходится писать руками?
На кой нам ОС с багами? Ну наверное затем, что не все в ней — баги.
Здравствуйте, ДимДимыч, Вы писали:
ДД>От ситуации зависит. В случае как у топикстартера, когда есть один непрерывно работающий поток, и второй — периодически модифицирующий данные, я бы использовал условную переменную для сигнализации.
В смысле, явно переключиться туда-обратно? А почему не пару семафоров?
Здравствуйте, ДимДимыч, Вы писали:
ДД>Если обеспечиваемая потоками функциональность идентична, то ИМХО с точки зрения затрачиваемых ресурсов дешевле отпускать один и тот же поток. А вот решение об идентичности функциональности должен принимать программист и с учетом этого выбирать методы синхронизации.
Алгоритмически, может и правда проще, не знаю, не занимался.
Но ведь логично реализовать не самую простую схему, а самую полезную, так? На мой взгляд, самая полезная (и "честная" чтоли) схема, это когда примитив захватывает тот, кто первым попытался его захватить. Ну или, по крайней мере, все, должны получать одинаковые возможности. А тут дедлок какой-то получается.
Точно также не согласен с тем, что плохой дизайн — держать мутекс захваченным (почти) всё время. Могу нафантазировать кучу примеров, когда это вполне логично и эффективно.
Здравствуйте, green.nsk, Вы писали:
GN>А позвольте поинтересоваться Вашей конфигурацией GN>У меня на ядрах 2.6 управление получает только тот поток, который первым захватил мутекс. На ядрах 2.4 всё работает, как Вы говорите
На 2.4 мутексы работают совсем по-другому, чем на 2.6. На 2.6 потоки сами между собой разбираются с помощью futex'а, на 2.4 используется отдельный служебный поток, который решает, кого разбудить.