Что такое потоки и как их использовать Версия 1.1 Лицензия: GNU FDL 1.3 Александр Киселёв ОС, потоки и процессы ● ● ● В операционной системе все запущенные приложения – процессы (два запущенных Блокнота это разные процессы). Внутри процесса могут быть потоки. Для того, чтобы всё выполнялось почти одновременно, ядро переключается между процессами и потоками. Процессов могут быть сотни => процессор не может их выполнять все сразу. ОС, потоки и процессы – 2 Применение в математике ● ● ● Т.к. потоки выполняются до некоторых пределов одновременно и легко пишутся по сравнению с процессами, с помощью них удобно распараллеливать математические расчёты. Часто задачу можно разбить на несколько подзадач, которые друг от друга не зависят. Каждую подзадачу можно вынести в отдельный поток, что увеличит скорость выполнения. Как они работают ● ● ● В любой программе есть хотя бы 1 поток – это основной код, int main(). В однопоточной программе реальное выполнение начинается с функции int main(). Для потока нужно написать функцию, которая будет служить аналогом int main(): void* название_функции (void* arg) { // содержимое функции } Создание потока ● Сначала надо создать идентификатор потока – его “имя”: pthread_t имя_потока; ● Теперь можно создавать сам поток: int pthread_create( &имя_потока, NULL, имя_функции_потока, (void*)&аргумент ); ● Не забудьте указать сверху файла #include <pthread.h> Как это выглядит 1й поток 2й поток phtread_create(...); int main() void* функция_потока(void* arg) phtread_join(...); Что такое аргумент ● ● Если это одна переменная, то её нужно преобразовать так: (void*)переменная. Пусть у нас есть две переменные, которые надо передать в нашу функцию. Надо: 1) Создать структуру из двух полей; 2) Создать экземпляр структуры; 3) Приравнять его полям значения переменных; 4) Передать её адрес при создании потока. Пример 1 struct ThreadArg { int a; int b; }; // структура параметров потока void* ThreadFunc(void* arg) { ThreadArg arguments = *((ThreadArg*)arg); // далее пользуемся arguments, как обычно } // ... int main() { // ... pthread_t mythread; ThreadArg args; // Param1,Param2 – параметры для потока args.a=Param1; args.b=Param2; pthread_create(&mythread, NULL, ThreadFunc, (void*)&args); // ... } Объединение ● Для объединения потоков применяется функция: int phtread_join(имя_потока,возврат); ● ● Объединение завершает этот поток. Чтобы ничего не возвращать, надо возврат заменить на NULL, а в конце функции написать return NULL; Возврат значения ● ● Для того, чтобы вернуть значение, нужно сделать указатель внутри функции потока. Создается он так: тип_данных* pointer = new тип_данных; ● Потом им можно пользоваться так: *pointer = какое-то значение; ● В конце функции пишется тогда так: return (void*)pointer; Возврат значения - 2 ● В main() нужно сделать вот что: void* tmp_result; T result; // временная переменная // T – тип возращаемой штуки // Может быть числом, структурой и т.д. pthread_join(имя_потока, &tmp_result); // объединяем потоки result = *( (T*)tmp_result ); // теперь можно пользоваться result! Проблема общих данных ● ● ● Обычно потоки разделяют между собой общие данные. Возникает проблема – потоки могут одновременно менять одну и ту же переменную. Особенно учитывая то, что нельзя предугадать, в какой последовательности они изменят переменную. Решение проблемы: Mutex ● ● ● Для решения проблемы придумали Mutex. Принцип такой – когда один из потоков начинает работать с общими данными, он блокирует mutex. Тогда все остальные потоки засыпают и ждут, пока первый всё сделает с данными. После разблокировки mutex продолжается параллельное выполнение. Использование Mutex ● Сначала надо создать mutex: pthread_mutex_t имя_мьютекса = PTHREAD_MUTEX_INITIALIZER; ● Теперь им можно пользоваться. Блокировать мьютекс можно так: pthread_mutex_lock(&имя_мьютекса); ● Разблокируется он так: pthread_mutex_unlock(&имя_мьютекса); Внимательность – это важно! ● ● Надо всегда помнить, что, пока мьютекс заблокирован, остальные потоки спят. Поэтому мьютекс блокируется только на время работы с общими данными. Иначе всё преимущество многопоточности будет утеряно. Источники ● Daniel Robbins. POSIX threads explained. ● Introduction to Programming Threads, MIT. ● POSIX thread (pthread) libraries. ● Thread (computer science) на Википедии. ● www.tsya.ru Приложение. Углубленные сведения. Зачем нужны потоки ● ● ● Потоки круче процессов тем, что затраты на их создание ощутимо ниже. А еще их легче кодить, ибо у потоков одного процесса (читай, программы) разделяют общую память. На современных процессорах может выполняться много потоков/процессов одновременно. На Pentium 4 и Core 2 Duo – 2 потока, на Core i3/i5 – 4, на Core i7 – 8. Указатели ● ● Указатель – в нем хранятся не данные, а адрес, по которому лежат в памяти данные. Они определяются так: T* имя_указателя; // T – тип данных ● ● Получить значение по адресу можно при помощи выражения *имя_указателя. Создать данные в памяти можно так: имя_указателя = new T; Что такое void* ● ● ● void* - это обобщенный указатель. Т.е., он может указывать на всё, что угодно. Его удобно использовать в pthread_create, т.к. заранее не ясно, каким будет аргумент. Размеры разных типов разные, а у указателей – один и тот же, равный разрядности системы (обычно 32 бита). Перейти к нормальному типу можно при помощи (T*)имя; История ● ● 1.0 – изначальная версия; 1.1 – исправлены опечатки, уточнены некоторые формулировки.