Сразу прошу извинить меня, если баян, поиск почему-то отказался мне помочь.
Я вообще редко пишу под *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 используется отдельный служебный поток, который решает, кого разбудить.
Здравствуйте, ДимДимыч, Вы писали:
ДД>От ситуации зависит. В случае как у топикстартера, когда есть один непрерывно работающий поток, и второй — периодически модифицирующий данные, я бы использовал условную переменную для сигнализации.
Я согласен, что мой пример "синтетический" и врядли имеет отношение к жизни, я просто проиллюстрировал проблему. В реальной программе всё не совсем так, но суть остаётся такая же: потоки, у которых заведомо одинаковый приоритет, могут заблокировать друг друга.
Здравствуйте, green.nsk, Вы писали:
Pzz>>В смысле, явно переключиться туда-обратно? А почему не пару семафоров?
GN>потому что с семафорами всё также по-уродски работает
Здравствуйте, green.nsk, Вы писали:
GN>Я согласен, что мой пример "синтетический" и врядли имеет отношение к жизни, я просто проиллюстрировал проблему.
Единственная задача мютекса — предотвратить противоречивое состояние данных. Для управления планированием должны использоваться иные механизмы.
GN>В реальной программе всё не совсем так, но суть остаётся такая же: потоки, у которых заведомо одинаковый приоритет, могут заблокировать друг друга.
В данном случае они друг друга не блокируют, один ведь продолжает работать. Заблокироватся могут и с разными приоритетами, если например не соблюдают порядок захвата ресурсов.
Обязательно бахнем! И не раз. Весь мир в труху! Но потом. (ДМБ)
Здравствуйте, green.nsk, Вы писали:
GN> А тут дедлок какой-то получается.
ну что-то же работает значит не deadlock
GN>Точно также не согласен с тем, что плохой дизайн — держать мутекс захваченным (почти) всё время. Могу нафантазировать кучу примеров, когда это вполне логично и эффективно.
Здравствуйте, Pzz, Вы писали:
Pzz>>>Явная бага в реализации мутексов.
А>>Почему бага?
Pzz>Потому, что и в том случае должны были бы работать оба потока. Попытка захвата мутекса должна иметь как минимум семантику вставания в очередь желающих захватить мутекс (прямо в POSIX'е этого требования нет, но оно следует из здравого смысла).
>Как максимум, должна еще учитываться проблема инверсии приоритетов — это когда более приоритетный поток вынужден слишком долго ждать менее >приоритетный, которому посчастливилось захватить мутекс первым.
а вот это в POSIX есть:
When threads executing with the scheduling policy SCHED_FIFO, SCHED_RR, or
SCHED_SPORADIC are waiting on a mutex, they shall acquire the mutex in priority order when the mutex is
unlocked.
и я думаю реализовано, и если поднять приоритет одному из них, то именно он будет держать mutex
Под MS Windows всё нормально
Стало быть, такое поведение планировщика может не всех устраивать. Возможно, поможет вызов pthread_attr_setschedpolicy с другой политикой.
Здравствуйте, green.nsk, Вы писали:
А>>кстати а почему просто не повысить приоритет потоку который никак не может захватить mutex, А>>в данном примере это не понятно какой (у меня управление получал и первый и второй), А>>но судя по описанию есть какой-то один который так и не может захватить мьютекс.
GN>А позвольте поинтересоваться Вашей конфигурацией GN>У меня на ядрах 2.6 управление получает только тот поток, который первым захватил мутекс. На ядрах 2.4 всё работает, как Вы говорите
а ядро 2.6.23 вы пробовали? у меня на нем потоки работали попеременно
Здравствуйте, mr_jek, Вы писали:
_>а вот это в POSIX есть: _> When threads executing with the scheduling policy SCHED_FIFO, SCHED_RR, or _> SCHED_SPORADIC are waiting on a mutex, they shall acquire the mutex in priority order when the mutex is _> unlocked.
_>и я думаю реализовано, и если поднять приоритет одному из них, то именно он будет держать mutex
Я не совсем про то. Если поток с низким приоритетом уже держит мутекс, и он (мутекс) понадобился потоку с высоким приоритетом, то может получиться так, что поток в высоким приоритетом ждет мутекса, а поток с низким приоритетом не получает управления (из-за своего низкого приоритета), и поэтому мутекс не отдает. Это называется инверсия приоритета.
Классическое решение заключается в том, что если поток с высоким приоритетом заблокировался на мутексе, то потоку, который мутекс в данный момент держит, временно повышают приоритет — чтобы он побыстрее кончил свои делишьки и убрался с дороги.
Здравствуйте, mr_jek, Вы писали:
GN>>Точно также не согласен с тем, что плохой дизайн — держать мутекс захваченным (почти) всё время. Могу нафантазировать кучу примеров, когда это вполне логично и эффективно.
_>давайте хотя бы один пример
Задержался с ответом, простите. Напишу псевдокодом, надеюсь, смысл донесу:
array<SOCKET> clientsArray; // сокеты, через которые происходит общение с клиентами
MUTEX clientsArrayMutex; // мутекс, который регулирует доступ к этому массиву
// Эти два сокета связываются друг с другом на стадии инициализации
SOCKET workerSender; // сокет, через который мы отправляем команды worker'у
SOCKET workerListener; // сокет, через который worker получает команды
SEMAPHORE serverPause; // семафор, на котором worker висит, когда мы хотим его "приостановить"void worker()
{
fd_set cacheFdset;
bool reloadClients = true;
while(true)
{
fd_set selectTarget;
OwnMutex(clientsArrayMutex); // захватif (reloadClients)
{
FD_ZERO(&cacheFdset);
FD_SET(workerListener, &cacheFdset);
foreach(client in clientsArray) FD_SET(client, &cacheFdset);
reloadClients = false;
}
selectTarget = cacheFdset;
int selectResult = select(selectTarget, infinite);
if (selectResult < 0) break;
foreach(client in clientsArray)
{
if (FD_ISSET(client, &selectTarget))
HandleClientActivity(client);
}
ReleaseMutex(clientsArrayMutex); // освобождениеif (FD_ISSET(workerListener, &selectTarget))
ProcessWorkerCommand();
} // while
}
// ну и напримерvoid AddClient(SOCKET newClient)
{
OwnSemaphore(serverPause);
SendCommand("reload clients");
OwnMutex(clientsArrayMutex);
clientsArray.Add(newCLient);
ReleaseMutex(clientsArrayMutex);
ReleaseSemaphore(serverPause);
}
На мой взгляд, в этом случае достаточно разумно держать мутекс "почти всё время". Конечно, здесь, в отличие от примера в первом посте, ничего плохого не произойдёт, если сервер честно висит на serverPause внутри ProcessWorkerCommand(). С указанным в первом посте примером имеет мало общего и приведён для того, чтобы подтвердить тезис о том, что "держать мутекс (почти) всё время — необязательно признак плохого дизайна".
Можно, конечно, обсуждать применение именно мутексов/семафоров в данном случае, но нафантазировать другие (более-менее классические) примитивы синхронизации, которые могут что-то сильно упростить в этом случае, у меня не получается (уж не говоря про то, что, например, в winapi набор примитивов синхронизации достаточно ограничен, а целесообразность конструирования более сложных из доступных — сомнительна).
Здравствуйте, Аноним, Вы писали:
А>а ядро 2.6.23 вы пробовали? у меня на нем потоки работали попеременно
Нет, не пробовал. К сожалению, под рукой ничего свежее 2.6.18 нет, а пересобирать ради этой фигни лень Если найду — может попробую, а так — очевидно, что исправлять в своём коде придётся. Просто порадуюсь, что в ядре всё же происходят изменения к лучшему.
Здравствуйте, Pzz, Вы писали:
Pzz>Если поток с низким приоритетом уже держит мутекс, и он (мутекс) понадобился потоку с высоким приоритетом, то может получиться так, что поток в высоким приоритетом ждет мутекса, а поток с низким приоритетом не получает управления (из-за своего низкого приоритета), и поэтому мутекс не отдает. Это называется инверсия приоритета.
Но если не ошибаюсь, такое может возникнуть только тогда, когда еще как минимум один поток с высоким приоритетом?
Обязательно бахнем! И не раз. Весь мир в труху! Но потом. (ДМБ)
Здравствуйте, ДимДимыч, Вы писали:
ДД>Но если не ошибаюсь, такое может возникнуть только тогда, когда еще как минимум один поток с высоким приоритетом?