Написание многонитевых приложений

Для написание многонитевых Motif приложений требуется знание следующих вопросов:

Почему многонитевость?

Существует два пути реализации параллельности программы: посредством двух и более процессов или же двух и более нитей. Первый подход известен как многопроцессность, а последний как многонитевость. Существует две основных причины выбора многонитевости а не многопроцессности: многопроцессность требует дорогостоящего межпроцессного взаимодействия, когда различные нити могут непосредственно совместно использовать данные. В сравнении с созданием процесса и переключением контекста процесса, создание нити и переключение контекста нити являются недорогиим операциями.

Цикл обработки ввода

Motif приложение основано на парадигме в которой приложение работает в бесконечном цикле, проверяя наличие ввода и диспетчеризируя ввод в соответствующие места. Ввод состоит из событий X, поступающих от сервера дисплея, ввода из альтернативных источников ввода и значений таймаутов. Код цикла обработки ввода выглядит приблизительно таким образом:

    while(TRUE) {
        XEvent event;
        XtAppNextEvent(app, &event);
        XtDispatchEvent(&event);
    }

app ссылается на контекст Motif приложения. XtAppNextEvent удаляет и возвращает событие из вершины очереди событий X. Если очередь бобытий X пуста, то XtAppNextEvent ожидает поступления события X, между тем просматривая альтернативные источники ввода и значения таймаутов и вызывая все процедуры обратного вызова активированные ими. После возврата из XtAppNextEvent XtDispatchEvent диспетчеризирует событие X в соответствующее место. Диспетчеризация событий X обычно инициирует вызов некоторых обработчиков событий, процедур действий, процедур обратного вызова ввода и таймеров, совместно называемых "процедурами обратного вызова".

Обычно каждое Motif приложение выполняет свою работу в процедурах обратного вызова. Если работа, выполняемая в процедуре обратного вызова требует длительного времени (и невидима), обработка следующего события в очереди событий X может быть заметно задержана, являясь причиной ухудшения отзывчивости приложения. Многонитевость может помочь преодолеть задержки в процессе обработки событий X и таким образом облегчить проблемму плохой отзывчивости (или интерактивности).

Тем не менее, это не единственная мотивация. Другие состоят в более "естественной" структуре программы присущей параллельному Motif приложению и лучшей производительности такого Motif приложения на многопроцессорных машинах (или на конфигурациях из нескольких машин).

Интерфейс Xt для многонитевости

Motif очень тесно связан с X Toolkit. Интерфейсы X11R6 для многонитевых приложений требуются только если многонитевое приложение вызывает интерфейсы Motif/Xt из нескольких нитей. Вполне возможно написать многопоточное Motif приложение в котором интерфейсы Motif/Xt вызываются только из одной нити. В таких приложениях этот интерфейс может не потребоваться. В дополнение к этому интерфейсу X11R6 определяет функции и типы двнных, используемые в многонитевой программе, в файле X11/Xthreads.h. Поскольку нитевый интерфейс зависит от операционной системы, подключение этого файла вместо специфичного для системы файла улучшает переносимость.

Инициализация Xt для использования в нескольких нитях

Motif приложение которое создает несколько нитей должно произвести вызов XtToolkitThreadInitialize, которая инициализирует Xt для использования в нескольких нитях. XtToolkitThreadInitialize возвращает True если данный Xt поддерживает многопоточность, а иначе возвращает False.

XtToolkitThreadInitialize может быть вызван перед или после XtToolkitInitialize и может вызываться более чем один раз. Тем не менее, он не может быть вызван параллельно из нескольких нитей. Приложение должно вызвать XtToolkitThreadInitialize перед вызовом XtAppInitialize, XtSetLanguageProc, XtOpenApplication или XtCreateApplicationContext.

Использование XtAppLock и XtAppUnlock

Параллельность в Motif приложении может быть запрограммирована с использованием модели в которой каждый контекст приложения имеет одну нить. Это означает, что каждый цикл обработки событий в каждом контексте приложения модет работать независимо внутри собственной нити. Это важно в смысле сохранения данных и обработки событий внутри каждого контекста приложения от случайного разрушения.

Для блокировки и разблокировки контекста приложения, всех виджетов и дисплейного соединения приложение должно использовать XtAppLock и XtAppUnlock.

Все функции Motif и Xt, которые принимают контекст приложения, виджет или дисплейное соединение как параметр, непосредственно блокируют связанный с ними контекст приложения на протяжении выполнения функции. Таким образом, за вычетом нескольких исключений, приложению нет необходимости вызывать XtAppLock или XtAppUnlock. Первым исключением является ситуация в которой приложению необходимо атомарно выполнить некоторую последовательность вызовов функций Xt. В этой ситуации приложение должно заключить последовательность вызовов Xt в соответствующую пару из XtAppLock и XtAppUnlock. Например, если приложению хочет атомарно проверить и обнивить высоту виджета, оно должно сделать это следующим образом:

    XtAppContext app;
    Dimension    ht = 0;
    Widget       w;
    ...
    XtAppLock(app);
    XtVaGetValues(w, XtNheight, &ht, NULL);
    if((int)ht < 10) {
        ht += 10;
        XtVaSetValues(w, XtNheight, ht, NULL);
    }
    XtAppUnlock(app);

Другим исключением являются ресурсы, которые возвращаются как реальные сегменты памяти внутри экземпляра виджета. Примером таковых являются:

В этих случаях приложение также должно окружать вызов XtGetValues вызовом XtAppLock и XtAppUnlock, чтобы другая нить выполняющаяся на том же экземпляре виджета не смогла изменить значение ресурса. Приложению также необходимо сделать копию извлеченного ресурса.

Для предоставления полностью эксклюзивного доступа к глобальным структурам приложение должно использовать XtProcessLock и XtProcessUnlock.

Как XtAppLock так и XtProcessLock могут быть вызваны рекурсивно. Для разблокирования верхнего уровня XtAppUnlock и XtProcessUnlock должны быть вызваны такое же количество раз как и XtAppLock и XtProcessUnlock соответственно. Для одновременной блокировки контекста приложения и глобальных данных Xt сначала вызывается XtAppLock, а затем XtProcessLock. Для разблокировки сначала вызывается XtProcessUnlock, а затем XtAppUnlock. Порядок вызова очень важен для предотвращения взаимоблокировок.

Новый XtAppMainLoop

Как мы мжем видеть подпрограмма XtAppMainLoop в X11R5 является бесконечным циклом который вызывает XtAppNextEvent, а затем XtDispatchEvent. В многопоточно-безопасном Xt бесконечный цикл в XtAppMainLoop должет предохранять нить обработки ввода от выхода из XtAppMainLoop без одновременного выхода из самой нити. Эта ситуация может привести к следующим проблеммам:

Для решения этих проблемм Xt версии X11R6 преобразовал XtAppMainLoop. Он стал условным циклом, который вызывает XtAppNextEvent, а затем XtDispatchEvent, но теперь после каждого цикла обработки производит проверку не завершило-ли приложение работу с помощью нового флага выхода в контексте приложения. Если флаг выхода установлен в True, то XtAppMainLoop выходит из цикла обработки ввода. Следующий пример показывает реализацию новой XtAppMainLoop:

    XtAppContext app;
    do {
        XEvent event;
        XtAppNextEvent(app, &event);
        XtDispatchEvent(&event);
    } while(XtAppGetExitFlag(app) == FALSE);

Флаг выхода в контексте приложения может быть установлен в True посредством вызова XtAppSetExitFlag. Значение флага выхода может быть получено посредством XtAppGetExitFlag. Новая XtAppMainLoop предоставляет эффективный способ уничтожения контекста приложения без необходимости выхода из нити обработки ввода.

Уничтожение контекста приложения

В многонитевых Motif приложениях рекомендуется следующий способ уничтожения контекста приложения: после того как нить обработки ввода выходит из XtAppMainLoop и после того как она определяет, что никакая другая нить не ссылается на соответствующий контекст приложения, она вызывает XtDestroyApplicationContext. Используйте XtAppGetExitFlag для синхронизации между нитью обработки ввода и другити нитями. Вызывайте XtAppSetExitFlag в процедуре обратного вызова, ответственной за выход из приложения. Это заставляет соответствующий XtAppMainLoop вернуть управление, но не завершает нить обработки ввода.

Обработка событий в нескольких нитях

Когда несколько нитей параллельно вызывают функции обработки сообщений, они возвращаются в порядке "последним вошел первым вышел". По другому это выглядит так: если несколько нитей заблокированы для ввода на любой из процедур обработки событий, они помещаются в стек блокированных нитей контекста приложения. Когда же происходит какой-либо ввод, нить извлекается из стека и ей дается возможность обработать ввод.

Например, предположим, что нить A, являющаяся нитью обработки ввода, блокированна для ввода в XtAppMainLoop, т.е. она находится в стеке блокированных нитей контекста приложения. Предположим также, что одна из других нитей, нить B, обнаружила некоторую ошибочную ситуацию и приняла решение отобразить окно сообщения и временно забрать обработку ввода у нити A. Т.к. блокированные нити помещаются в стек, это позволяет нити B достаточно просто перехватить у нити A обработку ввода на себя. Способ, которым нить B делает это заключается в выполнении некторого подобия цикла обработки ввода внутри соответствующей пары XtAppLock и XtAppUnlock как показано ниже:

    /* этот код выполняется нитью, отличной от нити обработки ввода */
    XtAppContext app;
    Widget       error_dialog;
    XEvent       event;
    XtAppLock(app); /* Блокировка контекста приложения */
    XtPopup(error_dialog, XtGrabExclusive); /* отображение диалога ошибки */
    do { /* собственная обработка ввода */
        XtAppNextEvent(app, &:event);
        if(/* некторое условие вызванное событием */)
            XtDispatchEvent(&event);
    } while (/* некоторое условие */);
    XtAppUnlock(app);

Поскольку нить B вошла в XtAppNextEvent после того, как это сделала нить A, то нить B будет помещена в стек сверху нити A. Впоследствии, когда произойдет ввод, нить B будет извлечена из стека и произведет обработку ввода.