Здравствуйте, Alu, Вы писали:
Alu>Четвёртый вариант: Alu>Отказаться от IRunable в пользу указателей на ф-ии. Alu>Недостатки: необъектно и подедовски
Как раз необъектость может оказаться достоинством.
Что такое поток? Это некоторая функция, выполняемая асинхронно.
Вся затея с IRunnable (с единственным методом Run) делается лишь затем, чтобы реализовать замыкание функции средствами языка, изначально не поддерживающего замыкания.
Поэтому и говорят: "объекты — замыкания для бедных".
Есть два основных способа организации потоков
1) поток — это функция с параметрами; через эти параметры функция узнаёт, как именно можно связаться с другими потоками — например, посылать сообщения
2) поток — это функция с общими данными; этими данными совместно владеют: сам поток и, как минимум, тот, кто его запустил
Связанный с потоком объект — это как раз второй случай.
Дальше встаёт вопрос — как грамотно реализовать владение объектом.
— Договориться о времени жизни и оставить владение объектом за пределами рабочего потока. Хозяин, перед тем, как убить объект, ждёт завершения потока.
— Договориться и отдать владение объектом рабочему потоку. Создатель после запуска потока уже не вправе безусловно рассчитывать на валидность объекта, и работает с ним как-то косвенно. Это, на самом деле, (1); объект является теми самыми параметрами функции и контекстом её исполнения, и более ничем.
— Сделать честное совместное владение — на подсчёте ссылок. Один владелец — это создатель, другой владелец — поток. Когда оба отпустят, тогда объект и умрёт.
При работе с потоком у нас есть три сущности:
— инфраструктура для запуска потока (обеспечивающая передачу параметров и владения общими данными; создающая и регистрирующая хэндл; и т.д.)
— хэндл потока, через который можно следить за его состоянием (жив-мёртв)
— пользовательские данные
И если первые две ещё есть смысл инкапсулировать в один объект, то пользовательские данные — это уже необязательно.
Это я всё плавно подвожу к тому, что есть такая библиотека Boost.Thread, с которой можно брать пример.
А не с явы.
Поток не должен управлять временем жизни объекта. В общем случае (а мы говорим про пул потоков), поток не завершается а фактически выбирает из очереди очередной Irunable и вызывает метод Run и так далее пока его не остановят. А вот управлять временем жизни объекта в рамках данной реализации я вижу ровно три способа.
Первый: объект IRunable автоматический, существует глобально, уничтожается гарантированно после пула потоков, либо после того как им уже никто не пользуется, регулируется дизайном, например
Второй: объект одноразовый, создается динамически, запускается в пул, уничтожается самостоятельно, отработав свою порцию
class CSomeRunable : public IRunable
{
virtual void Run()
{
// execute any workdelete this;
}
};
{
pool.Start(new CSomeRunnable());
}
Третий: похож на второй метод за исключением того что производится подсчет ссылок и автоматическое удаление объекта произойдет тогда когда на объект больше никто не ссылается, то есть RefCount=0
Alu:
СМ>>Это субъективные недостатки, а есть объективные?
Alu>Отсутствие ОО подхода для меня есть объективный минус.
Хорошо сказано
Интересно, где в этом IRunable ОО подход используется? По-моему, это всего лишь уродливый костыль для запуска потока. Поточная функция Run — это деталь реализации, и ей нечего делать в пользовательском интерфейсе объекта. А если внутренней реализации объекта нужно выполнить какую-то функцию (с передачей в неё всех необходимых данных) в отдельном потоке, то это можно сделать через нормальный функциональный объект — функтор (именно функторы в C++ служат для упаковки функций и данных в одно целое). Абстракция Callable, основанная на статическом полиморфизме, более гибкая, чем IRunable, т.к. не вынуждает пользователя наследоваться о каких-то классов и позволяет паковать функциональные объекты из составляющих прямо сходу (а-ля boost::bind).
Добрый день.
Разрабатываю свой простой пул потоков. О существовании готовых профессиональных реализаций знаю. Цель — практически Just For Fun.
Дизайн хотелось бы иметь в стиле Qt::QThread и Java.
Первый вариант:
class IRunable
{
public:
virtual void Run() = 0;
virtual ~IRunable();
};
void Start(IRunable* _pcRunable); // < запускает метод Run() _pcRunable в отдельном потоке.
Т.е. пользователь пула потоков просто реализует интерфейс IRunable, переопределённый метод Run() будет запущен в отдельном потоке:
class CSomeRunable : public IRunable
{
virtual void Run(); // < Код пользователя, запускаемый в отдельном потоке.void AdditionalMethod();
};
Вопрос: Как корректно освободить ресурсы CSomeRunable, ведь такой объект будет использоваться двумя нитями сразу: нитью создающей объект и нитью исполняющей метод CSomeRunable::Run()
int main()
{
CSomeRunable* p = new CSomeRunable();
Start(p); // < Запуск CSomeRunable::Run() в отдельной нити.
// ....
p->AdditionalMethod();
// ....
delete p; /// Некорректно! Нет гарантии что объект не используется другой нитью.
return 0;
}
Недостатки:
При таком дизайне нужен отдельный активный механизм, который будет отслеживать завершился ли дочерний поток и когда тот завершится — удалит объект. Что-то типа активного сборщика мусора.
Второй вариант:
class IRunable
{
public:
virtual void Run() = 0;
virtual IRunable* Сlone() = 0; // < Создаёт копию объектаvirtual ~IRunable() ;
};
void Start(const IRunable& _rcRunable); // < Создаёт копию _rcRunable и запускает метод Run() копии в отдельном потоке.
// По завершении Run(), копия удаляется.
Недостатки:
1. Пользователю прийдётся реализовывать метод Сlone(), что может быть непросто для сложных объектов, имеющих средства синхронизации.
2. Пользователь теряет связь с активным объектом. Т.е. копия объекта должна сама сообщить свой адрес, чтобы пользователь мог с ней взаимодействовать из создающей нити.
Третий вариант:
Использовать потоковозащищённые умные указатели.
Недостатки:
Прийдётся тащить их из сторонних библиотек.
Четвёртый вариант:
Отказаться от IRunable в пользу указателей на ф-ии.
Недостатки: необъектно и подедовски
Буду благодарен как за модификации моих вариантов, так и за принципиально новые, с другим дизайном.
Спасибо.
и не забыть обезопасить себя от такого ошибочного использования
int main()
{
CSomeRunable p;
Start(&p);
}
Alu>Четвёртый вариант: Alu>Отказаться от IRunable в пользу указателей на ф-ии. Alu>Недостатки: необъектно и подедовски
Это субъективные недостатки, а есть объективные?
---
С уважением,
Сергей Мухин
Re: Дизайн пула потоков
От:
Аноним
Дата:
27.08.09 08:08
Оценка:
Не надо никаких сборщиков мусора. Пусть поток и удаляет объект когда его поток завершиться. А еще совершенно непонятно накуя нужна отдельная функция Start() в глобальной неймспейсе, почему она не мембер класса?
Re[2]: Дизайн пула потоков
От:
Аноним
Дата:
27.08.09 13:27
Оценка:
К>Дальше встаёт вопрос — как грамотно реализовать владение объектом. К>- Договориться о времени жизни и оставить владение объектом за пределами рабочего потока. Хозяин, перед тем, как убить объект, ждёт завершения потока. К>- Договориться и отдать владение объектом рабочему потоку. Создатель после запуска потока уже не вправе безусловно рассчитывать на валидность объекта, и работает с ним как-то косвенно. Это, на самом деле, (1); объект является теми самыми параметрами функции и контекстом её исполнения, и более ничем. К>- Сделать честное совместное владение — на подсчёте ссылок. Один владелец — это создатель, другой владелец — поток. Когда оба отпустят, тогда объект и умрёт.
Можно еще так — потоком полностью владеет объект, а не наоборот. Соответственно прежде чем объект удаляется он сам ждет когда завершится поток который он же сам и стартанул для себя самого. Владелец объекта работает лишь с объектом, а не с двумя сущностями одновременно.
Здравствуйте, <Аноним>, Вы писали:
А>Можно еще так — потоком полностью владеет объект, а не наоборот. Соответственно прежде чем объект удаляется он сам ждет когда завершится поток который он же сам и стартанул для себя самого. Владелец объекта работает лишь с объектом, а не с двумя сущностями одновременно.
Активный объект?
... << RSDN@Home 1.2.0 alpha 4 rev. 1237>>
Перекуём баги на фичи!
Re[4]: Дизайн пула потоков
От:
Аноним
Дата:
27.08.09 14:09
Оценка:
А>>Можно еще так — потоком полностью владеет объект, а не наоборот. Соответственно прежде чем объект удаляется он сам ждет когда завершится поток который он же сам и стартанул для себя самого. Владелец объекта работает лишь с объектом, а не с двумя сущностями одновременно. К>Активный объект?
А это так называется? Сенкс, буду знать.
void Start(IRunable* _pcRunable)
{
// Теперь у нас есть копия, за временем жизни которой мы можем управлять.
IRunable* pRunable = _pcRunable->Clone();
// ...
}
Ну или конечно счетчик ссылок. Конкретных способов реализации — масса.
Не стыдно попасть в дерьмо, стыдно в нём остаться!
Re[5]: Дизайн пула потоков
От:
Аноним
Дата:
28.08.09 11:01
Оценка:
К>Это нужно для того, чтобы передать потоку владение объектом (самим собой).
Разбираться в коде влом, но не надо потоку владеть объектом. Объект владеет потоком, а не наоборот.
Если хочется чтобы объект автоматически удалялся при разрушении потока — тогда да, зависимость следует развернуть на 180 градусов. А делать двухсторонюю зависимость очень некрасиво.
Здравствуйте, Аноним, Вы писали:
А>А еще совершенно непонятно накуя нужна отдельная функция Start() в глобальной неймспейсе, почему она не мембер класса?
На самом деле она мембер. Есть и другие, "сервисные" мембры для работы с потоком. Но для того чтобы сделать примеры более локоничными и понятными — описал их так
Настоящему индейцу завсегда везде ништяк!
Re[7]: Дизайн пула потоков
От:
Аноним
Дата:
28.08.09 12:46
Оценка:
А>>Разбираться в коде влом, но не надо потоку владеть объектом. Объект владеет потоком, а не наоборот. К>Объект владеет потоком, а кто-то владеет объектом. К>Тогда нужно перед уничтожением объекта убедиться, что поток остановлен.
Для этого у объекта есть деструктор или, лучше, (в связи с особенностями наследования в С++) какой нить иной метод удаления, где сам объект может спокойно остановить поток, прежде чем подохнет. Только не говорите мне что вы удаляете объекты путем прямого вызова free на них
Тому кто владеет объектом достаточно знать семантиру работы именно с объектом. А внутренними ресурсами которыми пользуется объект — память, файлы, сетевые подключения _и_ потоки — про них оставьте заботиться самому объекту.
Здравствуйте, <Аноним>, Вы писали:
А>Тому кто владеет объектом достаточно знать семантиру работы именно с объектом. А внутренними ресурсами которыми пользуется объект — память, файлы, сетевые подключения _и_ потоки — про них оставьте заботиться самому объекту.
Но мы же обсуждаем не клиентский объект, а некую сущность — представителя потока.
В минимальном виде это тупо хэндл. Минимальный вид не очень устраивает, потому что встаёт вопрос, как минимум, о передаче в поток чего-то более сложного, чем void*.
... << RSDN@Home 1.2.0 alpha 4 rev. 1237>>
Перекуём баги на фичи!
Re[9]: Дизайн пула потоков
От:
Аноним
Дата:
28.08.09 21:34
Оценка:
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, <Аноним>, Вы писали:
А>>Тому кто владеет объектом достаточно знать семантиру работы именно с объектом. А внутренними ресурсами которыми пользуется объект — память, файлы, сетевые подключения _и_ потоки — про них оставьте заботиться самому объекту.
К>Но мы же обсуждаем не клиентский объект, а некую сущность — представителя потока. К>В минимальном виде это тупо хэндл. Минимальный вид не очень устраивает, потому что встаёт вопрос, как минимум, о передаче в поток чего-то более сложного, чем void*.
Не надо все усложнять. Поток — это всего лишь асинхронный метод объекта, который не принимает параметров. Не надо плодить лишних зависимостей и сущностей.
Здравствуйте, Аноним, Вы писали:
А>Не надо все усложнять. Поток — это всего лишь асинхронный метод объекта, который не принимает параметров. Не надо плодить лишних зависимостей и сущностей.
Время жизни объекта и потока как собираешься согласовывать? Без дополнительных зависимостей и сущностей.
Перекуём баги на фичи!
Re[11]: Дизайн пула потоков
От:
Аноним
Дата:
29.08.09 00:33
Оценка:
А>>Не надо все усложнять. Поток — это всего лишь асинхронный метод объекта, который не принимает параметров. Не надо плодить лишних зависимостей и сущностей. К>Время жизни объекта и потока как собираешься согласовывать? Без дополнительных зависимостей и сущностей.
Я уже надцать раз повторял — объект прежде чем издохнуть _ждет_ поток который он же породил. Опционально ставит флажок типа "пора дохнуть" чтоб долго не ждать.
Никаких "рандеву" не надо. Поток исполняет метод объекта без параметров. Объект ждет завершения потока перед своим издыханием. Зачем здесь пользователю объекта знать про поток? Зачем потоку контролировать время жизни объекта? Пользователь объекта контролирует время жизни объекта, объект контролирует время жизни потока. Исключительно односторонняя линейная зависимость, безо всяких ОС. Как и должно быть в идеале в грамотной архитектуре приложения.
Здравствуйте, Николай Ивченков, Вы писали:
НИ>По-моему, это всего лишь уродливый костыль для запуска потока.
Этот "уродливый костыль" успешно используется в ряде библиотек/языков Примеры я приводил выше. Он прост, прозрачен и удобен. Не требует никаких вспомогательных средств. Что и требуется. Статический полиморфизм конечно привнесёт больше гибкости, но плата за это — увеличивается сложность разработки и сложность использования.
Здравствуйте, Аноним, Вы писали:
А>Я уже надцать раз повторял — объект прежде чем издохнуть _ждет_ поток который он же породил. Опционально ставит флажок типа "пора дохнуть" чтоб долго не ждать.
Это породит блокировки создоющего потока,что выглядит как-то неэффективно. "Пора дохнуть" ненадёжный вариант, т.к. его реализация ложится на пользователя.
А>>Я уже надцать раз повторял — объект прежде чем издохнуть _ждет_ поток который он же породил. Опционально ставит флажок типа "пора дохнуть" чтоб долго не ждать. Alu>Это породит блокировки создоющего потока,что выглядит как-то неэффективно.
Не породит при грамотном дизайне
Alu>"Пора дохнуть" ненадёжный вариант, т.к. его реализация ложится на пользователя.
Аксиома многопоточного программирования №..: Поток умереть может только по своей воле.
Как много веселых ребят, и все делают велосипед...
Alu:
Alu>Этот "уродливый костыль" успешно используется в ряде библиотек/языков
В Boost.Thread от него отказались в пользу функциональных объектов, в Thread support library нового стандарта C++ — тоже.
Alu>Он прост, прозрачен
С точки зрения того, кто реализует библиотеку для работы с потоками, — да. Но для пользователя библиотеки её внутренности не особо-то важны, IMHO.
Alu>Не требует никаких вспомогательных средств. Что и требуется.
Жёсткие какие-то требования, однако.
Alu>Статический полиморфизм конечно привнесёт больше гибкости, но плата за это — увеличивается сложность разработки и сложность использования.
ononim:
Alu>>"Пора дохнуть" ненадёжный вариант, т.к. его реализация ложится на пользователя. O>Аксиома многопоточного программирования №..: Поток умереть может только по своей воле.
Скорее, только в "удобный" для него момент. Порой бывает неприятно иметь дело с функцией, выполнение которой не может быть корректно остановлено в течение небольшого промежутка времени по требованию извне.
если не разрешать создавать в стеке объекты thread, то почему бы в твоем примере функцию Start не сделать запускаемой в потоке с параметром
указателем на объект? или я вопрос не понял?
аналог:
Alu>>"Пора дохнуть" ненадёжный вариант, т.к. его реализация ложится на пользователя. O>Аксиома многопоточного программирования №..: Поток умереть может только по своей воле.
Неправда, если подсистема с пулом потоков предусматривает возможность ее (подсистемы) отключение пользователем, то
"пора дохнуть" единственный вариант.
Веру-ю-у! В авиацию, в научную революци-ю-у, в механизацию сельского хозяйства, в космос и невесомость! Веру-ю-у! Ибо это объективно-о! (Шукшин)
Alu>>>"Пора дохнуть" ненадёжный вариант, т.к. его реализация ложится на пользователя. O>>Аксиома многопоточного программирования №..: Поток умереть может только по своей воле. НИ>Скорее, только в "удобный" для него момент. Порой бывает неприятно иметь дело с функцией, выполнение которой не может быть корректно остановлено в течение небольшого промежутка времени по требованию извне.
Если есть необходимость в TerminateThread — перепишите программу чтобы таковой необходимости не было. Никаких исключений. Если не можете переписать функцию, или "забить" на висящий в процессе поток — вынесите ее в отдельный процесс.
Как много веселых ребят, и все делают велосипед...
dad>если не разрешать создавать в стеке объекты thread, то почему бы в твоем примере функцию Start не сделать запускаемой в потоке с параметром dad>указателем на объект? или я вопрос не понял?
тут http://rsdn.ru/forum/cpp/3517991.1.aspx
Alu>>>"Пора дохнуть" ненадёжный вариант, т.к. его реализация ложится на пользователя. O>>Аксиома многопоточного программирования №..: Поток умереть может только по своей воле. dad>Неправда, если подсистема с пулом потоков предусматривает возможность ее (подсистемы) отключение пользователем, то dad>"пора дохнуть" единственный вариант.
Ну вы вощем-то повторили мою фразу. Владелец потока говорит потоку что пора дохнуть, и ждет пока тот издохнет. Поток видит что пора сдохнуть — и дохнет. По своей воле.
Как много веселых ребят, и все делают велосипед...
Здравствуйте, ononim, Вы писали:
O>Если есть необходимость в TerminateThread
"Пора дохнуть" и TerminateThread — не одно и тоже.
O>— перепишите программу чтобы таковой необходимости не было. Никаких исключений. Если не можете переписать функцию, или "забить" на висящий в процессе поток — вынесите ее в отдельный процесс.
1. Отдельный процесс решает проблему корректного освобождения не любых ресурсов.
2. Процессов может не быть.
.
People who are more than casually interested in computers should have at least some idea of what the underlying hardware is like. Otherwise the programs they write will be pretty weird (c) D.Knuth
Здравствуйте, Alu, Вы писали:
Alu>Добрый день. Alu>Разрабатываю свой простой пул потоков. О существовании готовых профессиональных реализаций знаю. Цель — практически Just For Fun.
В моем случае (называемом (чисто по-колхозному) — менеджер параллельных задач) присутствует такой набор объектов (интерфейсов)
— задача
— контроллер задачи
— поток
— менеджер задач
все вещи — со своим счетчиком ссылок.
потоки — одноразовые. В том смысле, что объект потока создает системный поток только один раз. Если поток остановился, то все.
---
контроллер владеет задачей, потоком задачи и менеджером (не дает ему удаляться)
---
менеджеру дают задачу. он возвращает контроллер.
у менеджера два служебных потока
— для отложенного запуска (сам не знаю нафига я его прикрутил)
— освобождение пула ресурсов: контроллеры (у него системные хендлы всякие), потоки для выполнения задач.
---
Кроме того, поскольку все это дело работает в DLL, есть еще один потока (типа супервизор потоков). Все потоки обязаны у него регистрироваться (с увеличением счетчика ссылок). Поток супервизора создается при создании первого "рабочего" потока и останавливается когда остановится последний.
В начале своей работы он делает LoadLibrary для своей DLL, выход по FreeLibraryAndExitThread (типа как то так она называется).
---
Основная головная боль во всем этом — вопросы синхронизации старта/останова.
Да и вообще, для управления всем этим добром привлечено достаточно много кода... само добро — в районе 100K кода. Как говорится, почувствуйте разницу между CreateThread и "безопасным" управлением потоками
-- Пользователи не приняли программу. Всех пришлось уничтожить. --