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

Одним из самых распространенных вариантов применения этой технологии является «скидывание» в отдельный поток какой-нибудь достаточно продолжительной операции, для того чтобы иметь возможность корректно реагировать на события пользовательского интерфейса в главном потоке программы. Если вы запустили какую-либо операцию в программе, а она не реагирует на ваши действия, то скорее всего в данном случае существует только один главный поток.

Использование потоков в программировании — огромный пласт как теоретического, так и практического знания. В этот раз мы остановимся на применении потоков в C++ и инкапсуляции потоков в классе.

К сожалению, невозможно без дополнительных ухищрений использовать в качестве функции потока функции—члены класса. Во-первых, обычно для функции, содержащей тело потока, задана вполне конкретная сигнатура, которую программист не может изменить. Вторым камнем преткновения является то, что при вызове функции—члена в C++ используется неявный параметр — указатель на экземпляр класса.

На самом деле никто не запрещает применять простую функцию в стиле Си. Однако такой подход лишит нас прозрачности и легкости понимания кода.

Первый способ, который можно придумать, — использование статической функции — члена класса с явным параметром, ссылкой на объект этого класса. Такая функция будет «заглушкой» для вызова «настоящей» функции-члена после явного преобразования параметра к объекту класса.

class PThreadedAdapter

{

protected:

HANDLE workerThread;

bool workerLive, isThreadRunning;

static DWORD WINAPI ThreadStub(LPVOID

lParameter) {

((PThreadedAdapter*)lParameter)->ThreadProc();

return 0;

}

virtual void ThreadProc(void)=0;

}

//Поток создается следующим образом:

::CreateThread(NULL,0,(LPTHREAD_START_

ROUTINE)PThreadedAdapter::ThreadStub, this, 0,

&dwWorkerID);

Функция—член класса ThreadProc должна будет содержать в себе то, что поток будет выполнять, т.е. для применения этого «трюка» требуется создать новый класс, сделать описанный PThreadedAdapter родительским для него и реализовать чисто виртуальную функцию ThreadProc. В функции ThreadProc нужно установить «места прерывания» и проверять в них переменную isRunning. Если isRunning становится false, то необходимо корректно завершить работу и установить workerLive в false. Это сообщит родительскому потоку, что потоковая функция успешно закончила свою работу.

Для удобства добавим еще функции Start и Stop, которые, как видно из их имен, будут запускать и останавливать поток, встроенный в наш класс.

void PThreadedAdapter::Start(void) {

workerLive = true;

// Если будет false, то поток сразу остановится

DWORD dwWorkerID;

workerThread = ::CreateThread(NULL,0,

(LPTHREAD_START_ROUTINE

PThreadedAdapter::ThreadStub, this, 0,

&dwWorkerID);

if(!workerThread) { // Запустить не удалось

workerLive = false; isRunning = false;

}

}

void PThreadedAdapter::Stop() {

workerLive = false;

while(isRunning) {

Sleep(waitTime);

}

}

За рамками этой небольшой статьи осталось очень многое из мира потоков. Для самостоятельной работы предлагаю вам реализовать шаблонный класс-обертку для потокового класса или рассмотреть, как устроены и используются потоки в библиотеке Boost.

Купить номер с этой статьей в PDF
4341