Имеется главный поток создающий дочерние потоки с атомарным инкриментом счетчика потоков (lock inc qword[var]), вторичные потоки могут в произвольное время закрываться с атомарным декрементом этого счетчика, все потоки при включении глобального события должны закрыться но в строго определенной последовательности: сначала все вторичные потоки потом только главный поток, для этого главный поток должен ждать когда счетчик вторичных потоков будет равняться нулю, как это лучше реализовать? Вижу два варианта: 1 вариант. Основной поток: При создании вторичного потока: атомарно инкриминируем счетчик создаем вторичный поток ... При закрытии: Проверяем счетчик если =0 выходим из потока Ждем 1 мили секунду Переходим к п.1 Вторичные потоки: При закрытии: атомарно декременируем счетчик. ... Все работает на ура но есть большие потери времени на ожидание. 2 вариант. Основной поток -При инициализации: Создать заранее выкл-е событие закрытия всех потоков отправки, поместить его указатель в общий для всех потоков блок памяти. Основной поток будет ждать когда последний вторичный поток при закрытии включит его. При создании вторичного потока: атомарно инкриминируем счетчик создаем вторичный поток ... При закрытии: Проверяем счетчик если =0 выходим из потока Ждем событие если событие включено выходим. ... Вторичные потоки -При запуске : если счетчик =1 то выкл-м событие и выход ... -При завершении: если счетчик =1 то вкл-м событие и выход атомарно декременируем счетчик ... Потери времени в ожидании минимальны, однако много всяких ошибок связанных с синхронизацией, например,если основной поток успеет дважды инкриминировать счетчик (создав еще один поток) до того как первый вторичный поток дойдет до п.1 при запуске то событие не будет включено ( вынести эту проверку в главный поток не удалось, появилась другая проблемам синхронизации), таких проблем в этом методе много. Есть еще третий вариант связанный с обьектами синхронизациями (идеально подошли бы srw локи эксклюзив для основного потока и шара для вторичных) но тут созданый вторичный поток должен успеть взять объект синхронизации до того как сработает глобальное событие закрытия и объект возьмет основной поток, что 100% гарантировать невозможно. Как мне согласовать закрытия вторичных потоков без больших потерь времени и ошибок?
Есть такая тредовая операция под названием join. Идея в том, что тред вызывающий join блокируется, до тех пор пока не завершится поток, на который ссылается аргумент вызова join. Соответственно, завершение основного потока легко можно организовать таким циклом (на примере pthreads): Код (Text): foreach t in running_threads; do pthread_join(t, NULL); done
Я эту функцию первый раз вижу, но предположу что это потребует хранить хэндлы потоков (лишний расход памяти, дополнительный код обработки), к тому же хотелось бы оставить возможность отмены ожидания закрытия вторичных потоков основным потоком по истечению времени (например если через 10 сек поток не закрывается сам то закрыть его через терминейт сред и работать дальше)
Sercher Естественно. Это из POSIX Threads. вынь32апи для меня тёмный лес. Вам придётся поискать аналог pthread_join на MSDN. Он должен быть, это достаточно распространённая операция которая попадается во всяких разных потоковых библиотеках, не только в pthreads. Да. Но я бы не назвал это расходом памяти. Сколько у вас потоков? 100? Если даже в 64-х-битной системе, с восьмибайтным адресом, это по 16 байт на хендл при хранении в виде односвязного списка. Это полтора килобайта. Учтите, что стек каждого (одного!) потока запросто может отъесть в два раза больше. А может и в двадцать раз больше. Я не знаю точно сколько, но вы загляните в документацию из любопытства. Код обработки? Односвязный список. Двадцать строк кода. Даже если с синхронизацией. Вешаем таймер, в обработчике которого делаем detach для каждого потока, кроме основного. Основной поток после этого выходит из цикла ожидания завершения потоков. Кстати для этих детачей, очень неплохо было бы иметь список потоков. ps. Я тут подумал, что ежели односвязный список, то цикл будет выглядеть несколько иначе: Код (Text): while !is_empty(running_threads); do pthread_join(first(running_threads), NULL); done; Но это, собственно, уже мелочи жизни.
спасибо, если ничего лучше не найду, создам блок памяти в кучи под массив указателей и буду реалокить его по мере надобности. п.с. двунаправленный список в данном случае лучше заменить масштабируемой битовой маской.
Sercher Во-первых, терминология мне не совсем понятна. Двунаправленный -- это double linked list? Я думал что их переводят как двусвязные... Но без разницы. Я не предлагал использовать двунаправленный список. Двунаправленный -- много чести будет, если все операции которые нужны -- это push и pop. Тут можно и стеком обойтись фиксированного размера. Во-вторых -- ваше дело. Мне задача неизвестна, и поэтому я могу лишь гадать о том какие структуры данных будут для задачи лучше. Могу предложить и другой вариант. Вместо списка запущенных потоков можно хранить список завершённых. Ну, точнее, список завершающихся, поскольку добавление элементов в него будет выполнять завершающийся поток. А главный поток перед завершением из этого списка выудит элемент за элементом, и на каждый поток сделает join. Отслеживая при этом количество запущенных потоков в отдельной переменной типа int. Если потоки часто создаются/завершаются, и к моменту завершения главного потока, список вырастает до неприличных размеров, то можно придумать какой-нибудь ещё механизм извлечения, который будет работать параллельно с работой основного потока. Кстати, программа с окошками возится? Там есть цикл обработки ивентов? Если есть, то почему бы не засылать ивенты о завершении потоков? И вместо создания самостоятельного списка завершённых процессов, заюзать под эти цели очередь сообщений. Тут проще-то не выйдет. Проблема ведь в том, как я понимаю, что непременно хочется, чтобы главный поток завершился бы только после того, как завершатся все остальные потоки. А остальные потоки, будут сначала сообщать, потом завершатся. Не важно каким образом они будут сообщать: ивентами, семафорами, через conditional variable... Без разницы. Никто не сможет гарантировать, что когда главный поток получает "сообщение" дочернего, этот дочерний уже успел завершится. Единственный способ убедиться в этом -- это join выполняемый главным потоком на дочернем. А для этого, главный поток должен знать id этого потока. При этом, как правило, все эти библиотеки для трединга не хранят списка потоков. Так что либо мы будем хранить сами, либо... Либо забъём на строгий подход к проблеме, и будем завершать главный поток дожидаясь не завершения дочерних, а лишь сигнала о том, что все дочерние закончили все свои дела, ради которых их и запускали. А вот если мы забиваем на строгость, то тогда надо найти аналог POSIX'овых conditional variables, и просто блочить основной поток на такой переменной-счётчике потоков пока она не станет равной нулю. А когда станет, просто делать exit(0);
Sercher Можно вообще сделать без счётчиков и явных списков. Т.е. список можно хранить неявно, распределив его по потокам: Вторичные потоки: 1) Каждый вторичный поток при создании в качестве параметра получает хендл последнего созданного перед ним потока. 2) Когда вторичный поток завершается, он в качестве ExitCode возвращает переданный ему хендл. Первичный поток: 1) Когда первичный поток готов ожидать завершения всех созданных потоков, он ждёт на хендле последнего созданного потока. 2) Как только прерывается ожидание на хендле последнего созданного потока, первичный поток вызывает GetExitCodeThread закрывает хендл мёртвого потока и ждёт на полученном хендле. Т.о. он прождёт на хендлах всех созданных потоков. Правда, в таком варианте недопустимо использование TerminateThread. Но при правильной реализации TerminateThread вообще не должна понадобиться.
r90 Из-за (lock inc qword[var]) штоле? Не, посмотрите в WASM.LANG.C - там в каждом втором "топике про C++" сплошные ассемблерные вставки
Не совсем понимаю как здесь можно семафор использовать? мне не надо ограничивать кол-о потоков по счетчику потоков, а заставить ждать один из потоков когда счетчик будет =0 и все потоки закроются. Самый безопасный и быстрый способ это хранить хендлы вторичных работающих потоков в массиве, и ждать основным потоком WaitForMultipleObject (блоком по 64 шт) когда они все перейдут в сигнальное состояние. Можно как l_inc советует, но поскольку передавать в создаваемый поток можно только один параметр который как правило уже занят, нужно создавать структуру куда будет входить хендл и передавать указатель на нее. Проблемы возможны при зависание хоть одного потока, или его неправильном закрытии (без возврата значения), а во всем остальном очень хорошее решении. (Понятно что код должен быть без ошибок ведущих к зависания, неправильной работе потоков и тд. но когда программа с большим функционалом пишется несколькими постоянно меняющемся программистами с различными уровнями знаний они неизбежны, поэтому лучше пере... чем недо...) Пока оптимальнее первого нечего не нашел.