Синхронизация потоков необходима тогда, когда действия потоков зависят друг от друга

LPSECURITY_ATTRIBUTES lpThreadAttributes, // дескриптор защиты SIZE_T dwStackSize, // начальный размер стека LPTHREAD_START_ROUTINE lpStartAddress, // функция потока LPVOID lpParameter, // параметр потока DWORD dwCreaonFlags, // опции создания LPDWORD lpThreadId // идентификатор потока

Кроме рассматриваемых выше функций библиотеки С для работы с потоками можно использовать и функции WinApi 32:CreateRemoteThread, CreateThread, ExitThread, GetExitCodeThread, GetThreadPriority, ResumeThread, SetThreadPriority, CreateProcess.

HANDLE CreateThread(

);

Член структуры lpSecurityDescriptor определяет дескриптор безопасности для нового потока. Если lpThreadAttributes имеет значение ПУСТО (NULL), поток получает заданный по умолчанию дескриптор защиты. Списки контроля доступа (ACL) в заданном по умолчанию дескрипторе безопасности для потока поступают из первичного маркера или маркера заимствования прав создателя.

dwStackSize - Начальный размер стека, в байтах. Система округляет это значение до самой близкой страницы памяти. Если это значение нулевое, новый поток использует по умолчанию размер стека исполняемой программы.

lpParameter - Указатель на переменную, которая передается в поток.

dwCreationFlags - Флаги, которые управляют созданием потока. Если установлен флаг CREATE_SUSPENDED, создается поток в состоянии ожидания и не запускается до тех пор, пока не будет вызвана функция ResumeThread. Если это значение нулевое, поток запускается немедленно после создания.

pThreadId - Указатель на переменную, которая принимает идентификатор потока.

Замечание.Следует обратить внимание на изменения в make-файле, которые необходимо сделать для создания многопоточного приложения.

На этапе компиляции переменную CFLAGS нужно заменить переменной CFLAGSMT. Это та же переменная, но с добавлением флага _MT. Это приводит, в частности, к тому, что компилятор вставляет имя файла libcmt.lib вместо libc.lib. Эта информация используется компоновщиком.

При создании многопоточного приложения в среде MS-Visual C++необходимо при помощи пункта меню “Settings” внести изменения в опции проекта в диалоге “Project Settings”:

· · закладка “C/C++” - в поле “Preprocessor definition” добавить флаг _MT;

· · закладка “Link” - в поле “Object/library modules” добавить библиотеку libcmt.lib, затем включить переключатель “Ignore all defaults libraries”.)

Примерные действия по созданию потока

Каждый поток имеет свою функцию – функцию потока. Пока выполнение этой функции не завершено, поток продолжает оставаться выполняемым. Эта функция должна быть объявлена следующим образом(в качестве имени потоковой функции выберем ThreadFun):



void ThreadFun(void *lpvoid);
  • Поток, создающий другой поток, может передавать через параметр lpvoid создаваемому потоку информацию в виде 32-разрядной переменной. Обычно эта переменная является указателем на блок данных (например, на структуру данных) для хранения общих переменных для потоков. Это дает возможность создающему и создаваемому потокам совместно владеть информацией без использования глобальных переменных (общая память потоков).

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

Пусть созданием и остановкой потока, а также типом выполняемого потоком действия (видом рисунка), управляет пользователь при помощи команд меню, имеющих идентификаторы:

#define IDM_CIRCLES 1 #define IDM_BARS 2 #define IDM_STOP 3 #define IDM_BEGIN 4

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

struct PARAM { BOOL type; // тип выполняемого потоком действия HWND wnd; // окно, в оконной процедуре которого создается поток BOOL stop; // признак завершения работы потока };

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

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { static struct PARAM p; // используется как общая память потоков . . . switch(msg) { case WM_COMMAND: // обработка сообщений от меню { switch(LOWORD(wParam)) { case IDM_BEGIN: // запуск потока на выполнение { // запись в общую память потоков, p.type=0; p.wnd=hWnd; p.stop=0; // создание и немедленный запуск потока _beginthread(ThreadFun,0,(void *)(&p)); }; break; case IDM_STOP: // полная остановка потока { // записать признак завершения потока p.stop=1;}; break; case IDM_CIRCLES: // изменить тип действия { // записать тип выполняемого потоком действия p.type=0;}; break; case IDM_BARS: // изменить тип действия { // записать тип выполняемого потоком действия p.type=1;}; break; } };return 0l; case WM_DESTROY: { // записать признак завершения потока p.stop=1; PostQuitMessage(0); }; return 0l; // обработка других сообщений . . . default: return DefWindowProc(hWnd, msg, wParam, lParam); } return 0l; }

Функция создаваемого вторичного потока в данном случае может иметь следующее определение:

void ThreadFun(void *lpvoid) { // автоматические переменные потока . . . // получение адреса общей памяти потоков struct PARAM *p=(struct PARAM *)lpvoid; while(!p->stop) // пока признак завершения не установлен { // выполнение действия, определяемогоp->type, при этом может // использоваться дескриптор создавшего поток окна, он хранится в p->wnd . . . Sleep(1000); // задержка на 1 сек } _endthread(); // полная остановка потока }

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

Приостановка выполнения потока

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

· Однако предположим, что во вторичном потоке необходимо реализовать анимацию. Обычно анимация в Windows осуществляется с использованием сообщения WM_TIMER. Но если вторичный поток не создает окно, то он не может получить это сообщение. А без задания определенного темпа анимация может осуществляться слишком быстро.

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

· Функция Sleep не осуществляет возврата до тех пор, пока не истечет указанное время. В течение него выполнение потока приостанавливается и выделения для него процессорного времени не происходит(не считая того незначительного количества времени, необходимого для того, чтобы система могла проверить, пора возобновлять выполнение потока или нет).

· Если параметр функции Sleep задать равным нулю, то поток просто будет лишен остатка текущего выделенного для него кванта времени.

· Когда поток вызывает функцию Sleep, задержка на заданное время относится только к этому потоку. Система продолжает выполнять другие потоки этого и других процессов.

Замечание.Нужно отметить, что если вызывать функциюSleep для потоков, создающих окна, то будет происходить замедление обработки сообщений для окон этого потока. Поэтому не рекомендуется вызывать эту функцию в первичном потоке.

Синхронизация потоков

· В однозадачной операционной системе программы обычно не нуждаются в “светофорах” для координации своих действий. Выполняется всегда только одна задача, все ресурсы при этом находятся в полном ее распоряжении.

· Даже в многозадачной операционной системе большинство программ выполняются независимо друг от друга. Но некоторые проблемы все же могут возникнуть. Например, двум программам может понадобиться читать и писать в один файл в одно и то же время. Для таких случаев операционная система поддерживает механизм разделения файлов (shared files) и блокирования отдельных фрагментов файла (record locking).

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

Например, один поток может обновлять переменные, а другой - использовать их. Иногда в этой ситуации может возникнуть проблема, а иногда нет.

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

Однако предположим, что потоки разделяют несколько переменных или структуру данных.

Часто сложные переменные или поля структур, разделяемые потоками, должны быть согласованными между собой. Операционная система может прервать поток в середине процесса обновления этих переменных. В этом случае поток, который затем использует эти переменные, будет иметь дело с несогласованными данными.

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

Отмеченные выше средства синхронизации можно разделить на критические секции и объекты ядра