Библиотека сайта rus-linux.net
Программирование с использованием gtkmm 3. Многопоточные программы
Оригинал: Programming with gtkmm 3Авторы: Murray Cumming, Bernhard Rieder, Jonathon Jongsma, Ole Laursen, Marko Anastasov, Daniel Elstner, Chris Vine, David King, Pedro Ferreira, Kjell Ahlstedt
Дата публикации: 15 Октября 2013 г.
Перевод: А.Панин
Дата перевода: 10 Апреля 2014 г.
29. Многопоточные программы
29.1. Ограничения
glibmm предоставляет достаточный набор функций для создания потоков, примитивов взаимных исключений (мьютексов), условных переменных и классов для локальных блокировок, необходимых для реализации многопоточных программ с помощью языка программирования C++.
Однако, требуется особая осторожность при разработке многопоточных программ на основе gtkmm, так как библиотека libsigc++, а также сам класс отслеживания sigc::trackable
не являются потокобезопасными. Это означает, что ни одно из сложных взаимодействий, происходящих на заднем плане при использовании библиотеки libsigc++, не защищено с помощью взаимного исключения или какого-либо другого примитива синхронизации.1
29.1.1. Правила разработки
sigc::trackable
, так как результаты работы созданных классов могут быть непредсказуемыми (следует обратить особое внимание на пункты 4 и 5 ниже).
- Используйте экземпляр класса обработчика событий
Glib::Dispatcher
для вызова функций gtkmm из рабочих потоков (более подробно об этом будет сказано в следующем разделе). - Объект сигнала на основе класса
sigc::signal
должен рассматриваться как объект, принадлежащий создавшему его потоку. Только этот поток должен соединять объект слота на основе классаsigc::slot
с объектом сигнала и только этот поток должен генерировать сигналы с помощью методаemit()
или использоватьoperator()()
для перегрузки операторов объекта сигнала, а также удалять любой присоединенный объект слота на основе классаsigc::slot
. Из этого следует (помимо остальных вещей) то, что любой объект сигнала, предоставленный виджетом из состава gtkmm, должен обслуживаться исключительно в рамках главного потока приложения с графическим интерфейсом и любой объект на основе унаследованного от класса отслеживанияsigc::trackable
класса, имеющий нестатические методы со ссылками из присоединенных к объекту сигнала слотов, должен уничтожаться из этого потока. - Любой объект соединения на основе класса
sigc::connection
должен рассматриваться как принадлежащий потоку, в рамках которого был осуществлен вызов метода, возвращающего этот объект типаsigc::connection
. Только в рамках этого потока должны вызываться методы данного объекта соединения типаsigc::connection
. - Объект слота на основе класса
sigc::slot
, создаваемый с помощью вызова методаsigc::mem_fun()
и ссылающийся на метод унаследованного от класса отслеживанияsigc::trackable
класса, никогда не должен копироваться в другой поток, а также уничтожаться в рамках того потока, который его не создавал. (Одно из последствий этого ограничения заключается в том, что методGlib::Threads::Thread::create()
не должен вызываться с передачей в качестве аргумента слота, созданного с помощью вызова методаsigc::mem_fun()
, представляющего метод такого унаследованного класса. Однако, методуGlib::Threads::Thread::create()
можно безопасно передавать объект функции, представляющий такой метод с помощью, скажем, экземпляра классаboost::bind
,std::bind
в C++11 или лямбда-выражения в C++11.) - В том случае, если класс, лежащий в основе определенного объекта, наследуется от класса отслеживания
sigc::trackable
, объекты слотов на основе классаsigc::slot
, представляющие любой из нестатических методов класса, должны создаваться в рамках одного потока с помощью вызоваsigc::mem_fun()
. Первый создавший такой слот поток должен рассматриваться как владелец соответствующего объекта, предназначенный для создания дополнительных слотов, ссылающихся на любой из нестатических методов с помощью данной функции, деактивации этих слотов путем разрыва соединения, а также для уничтожения объекта отслеживания. -
Хотя библиотека glib и является потокобезопасной сама по себе, любые использующие библиотеку libsigc++ обертки из состава glibmm не являются таковыми. Таким образом, к примеру, вызовы методов для управления циклом обработки событий
Glib::SignalIdle::connect()
,Glib::SignalIO:connect()
,Glib::SignalTimeout::connect()
,Glib::SignalTimeout::connect_seconds
, а также манипуляции с объектами соединений на основе классаsigc::connection
, возвращаемыми в результате вызовов этих методов, должны осуществляться из одного потока, в рамках которого функционирует цикл обработки событий.
Варианты методовconnect*_once()
:Glib::SignalIdle::connect_once()
,Glib::SignalTimeout::connect_once()
,Glib::SignalTimeout::connect_seconds_once()
являются потокобезопасными в любых случаях, когда слот не создается с помощью вызова методаsigc::mem_fun()
, который представляет метод класса, унаследованного от класса отслеживанияsigc::trackable
. Эта ситуация аналогична ситуации с вызовом методаGlib::Threads::Thread::create()
, описанной в пункте 4.
29.2. Использование класса обработчика событий Glib::Dispatcher
Слоты, соединенные с объектами сигналов на основе класса sigc::signal
исполняются в потоке, в рамках которого осуществляется вызов метода emit()
или используется operator()()
по отношению к объекту сигнала. Объект обработчика событий на основе класса Glib::Dispatcher
ведет себя отличным образом: соединенные слоты исполняются в том потоке, в котором объект обработчика событий был создан (в потоке, который должен содержать главный цикл обработки событий glib). В том случае, если объект обработчика событий создается в главном потоке обработки событий графического интерфейса (который также будет главным потоком приема событий), любой рабочий поток сможет с помощью него генерировать сигналы, причем все соединенные слоты будут безопасно исполнять функции gtkmm.
Также существует несколько правил безопасного использования потоков при работе с объектом обработчика событий на основе класса Glib::Dispatcher
. Как упоминалось ранее, объект обработчика событий должен создаваться в потоке приема событий (потоке, с помощью главного цикла обработки событий которого будут исполняться соединенные слоты). Обычно это главный поток программы, хотя существует и конструктор класса Glib::Dispatcher
, способный принимать объект контекста обработки событий на основе класса Glib::MainContext
любого потока, который содержит главный цикл обработки событий. Только поток приема событий может вызывать метод connect()
объекта обработчика событий на основе класса Glib::Dispatcher
или производить манипуляции с любым связанным объектом соединения на основе класса sigc::connection
при отсутствии дополнительного механизма синхронизации. Однако, любой рабочий поток может безопасно генерировать сигналы с помощью объекта обработчика событий на основе класса Glib::Dispatcher
без использования любых механизмов блокировки после того, как поток приема событий подсоединит слоты при условии, что этот поток был создан перед запуском рабочего потока (в том случае, если он был создан после запуска рабочего потока, для гарантии видимости потребуются дополнительные механизмы синхронизации).
Помимо того, что соединенные слоты всегда исполняются в рамках потока приема событий, объекты обработчика событий на основе класса Glib::Dispatcher
аналогичны объектам sigc::signal<void>
. Следовательно, они не позволяют ни передать неограниченное количество аргументов, ни вернуть значение. Лучшим способом передачи неограниченного количества аргументов является потокобезопасная (асинхронная) очередь. Хотя на момент написания этой книги в составе glibmm не имеется реализации подобного механизма, большинство создающих код для работы с множеством потоков разработчиков должно располагать реализацией такой очереди (эти реализации создаются относительно просто, хотя иногда и возникают сложности при комбинировании механизмов безопасного использования потоков и механизмов строгой безопасности генерации исключений).
Сигналы с помощью объекта обработчика событий на основе класса Glib::Dispatcher
могут генерироваться как из потока приема событий, так и из рабочих потоков, хотя это и должно делаться в разумных пределах. В Unix-подобных системах объекты обработчиков событий делят единственный неименованный канал, который в теории будет заполняться данными только в очень нагруженной системе при исполнении программы с очень большим количеством объектов обработчиков событий. В том случае, если канал заполнится данными перед тем, как как главный цикл обработки событий потока приема событий получит возможность прочитать из него все данные, а в рамках потока приема событий будет осуществлена попытка генерации нового сигнала с записью данных в канал в текущем состоянии, поток приема событий навсегда заблокируется на операции записи. При генерации событий из потока приема событий, конечно же, может использоваться и обычный объект sigc::signal<void>
.
29.3. Пример
Это пример программы с двумя потоками, причем один поток используется для обработки событий графического пользовательского интерфейса, как и во всех программах на основе gtkmm, а второй является рабочим потоком. Рабочий поток создается в момент нажатия вами кнопки "Начать работу". Он уничтожается тогда, когда выполнение работы завершается, тогда, когда вы нажимаете кнопку "Закончить работу", а также тогда, когда вы нажимаете кнопку "Выход".
Объект обработчика событий на основе класса Glib::Dispatcher
используется для отправки уведомлений из рабочего потока в поток обработки событий пользовательского интерфейса. Класс ExampleWorker
содержит данные, которые доступны обоим потокам. Эти данные защищены с помощью объекта взаимного исключения на основе класса Glib::Threads::Mutex
. Обновление состояния пользовательского интерфейса происходит исключительно из потока обработки событий пользовательского интерфейса.
Рисунок 29-1: Многопоточная программа
Файл: exampleworker.h
(Для использования совместно с gtkmm 3, а не с gtkmm 2)
Файл: examplewindow.h
(Для использования совместно с gtkmm 3, а не с gtkmm 2)
Файл: examplewindow.cc
(Для использования совместно с gtkmm 3, а не с gtkmm 2)
Файл: main.cc
(Для использования совместно с gtkmm 3, а не с gtkmm 2)
Файл: exampleworker.cc
(Для использования совместно с gtkmm 3, а не с gtkmm 2)
Эти взаимодействия происходят из-за того, что, помимо других вещей, наследуемый от класса отслеживания sigc::trackable
класс благодаря механизму наследования будет содержать объект типа std::list
, с помощью которого будут отслеживаться слоты, созданные с помощью вызовов метода sigc::mem_fun()
и представляющие любые из нестатических методов (точнее, он содержит список методов обратного вызова, который позволяет отсоединить соединенные слоты при уничтожении объекта). Каждый объект слота на основе класса sigc::slot
также сохраняет с помощью объектов на основе класса sigc::slot_rep
собственный объект отслеживания на основе класса sigc::trackable
для отслеживания любых объектов соединений на основе класса sigc::connection
, которые необходимы для выполнения операции его уничтожения, а также позволяют разорвать соединение с объектом отслеживания на основе класса sigc::trackable
при отсоединении или при уничтожении. Объекты сигналов на основе класса sigc::signal
также содержат список слотов, который будет обновляться при вызове их метода connect()
или при вызове методов любого объекта соединения на основе класса sigc::connection
, связанного с таким соединением.
Следующий раздел : Рекомендуемые техники.