на примере POSIX Threads, очень популярно

advertisement
Что такое потоки и как
их использовать
Версия 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 – исправлены опечатки, уточнены
некоторые формулировки.
Download