1. Лекция 6:
Многопоточное программирование
Часть 2
(Multithreading programming)
Курносов Михаил Георгиевич
к.т.н. доцент Кафедры вычислительных систем
Сибирский государственный университет
телекоммуникаций и информатики
http://www.mkurnosov.net
2. Программный инструментарий
Прикладные библиотеки
Intel Threading Building Blocks (TBB)
Microsoft Concurrency Runtime
Apple Grand Central Dispatch
Boost Threads
Qthread, MassiveThreads
Языки программирования
OpenMP
(C/C++/Fortran)
Intel Cilk Plus
С++11 Threads
C11 Threads
C# Threads
Java Threads
Erlang Threads
Haskell Threads
Системные библиотеки (System libraries)
Уровень
пользователя
(User space)
GNU/Linux Pthreads
Thread
Thread
Уровень ядра
(Kernel space)
Win32 API/.NET Threads
Thread
Thread
Thread
Apple OS X Cocoa, Pthreads
Thread
Thread
…
Thread
Kernel
thread
…
Kernel
thread
Системные вызовы (System calls)
Kernel
thread
Kernel
thread
Kernel
thread
Kernel
thread
Kernel
thread
Kernel
thread
Process/thread scheduler
Операционная система (Operating System)
GNU/Linux, Microsoft Windows, Apple OS X, IBM AIX, Oracle Solaris, …
Hardware (Multi-core processors, SMP/NUMA)
2
3. Программный инструментарий
Прикладные библиотеки
Intel Threading Building Blocks (TBB)
Microsoft Concurrency Runtime
Apple Grand Central Dispatch
Boost Threads
Qthread, MassiveThreads
Языки программирования
OpenMP
(C/C++/Fortran)
Intel Cilk Plus
С++11 Threads
C11 Threads
C# Threads
Java Threads
Erlang Threads
Haskell Threads
Системные библиотеки (System libraries)
Уровень
пользователя
(User space)
GNU/Linux Pthreads
Thread
Thread
Уровень ядра
(Kernel space)
Win32 API/.NET Threads
Thread
Thread
Thread
Apple OS X Cocoa, Pthreads
Thread
Thread
…
Thread
Kernel
thread
…
Kernel
thread
Системные вызовы (System calls)
Kernel
thread
Kernel
thread
Kernel
thread
Kernel
thread
Kernel
thread
Kernel
thread
Process/thread scheduler
Операционная система (Operating System)
GNU/Linux, Microsoft Windows, Apple OS X, IBM AIX, Oracle Solaris, …
Hardware (Multi-core processors, SMP/NUMA)
3
4. Коэффициент ускорения (Speedup)
Введем обозначения:
o
𝑻(𝒏) – это время выполнения последовательной программы
(sequential program)
o
𝑻 𝒑 (𝒏) – это время выполнения параллельной программы
(parallel program) на p процессорах
Коэффициент S ускорения параллельной программ (Speedup):
𝑺𝒑 𝒏 =
𝑻(𝒏)
𝑻 𝒑 (𝒏)
Коэффициент ускорения 𝑆 𝑝 𝑛 показывает во сколько раз
параллельная программа выполняется на p процессорах быстрее
последовательной программы при обработке одних и тех же входных
данных размера n
Как правило 𝑆 𝑝 𝑛 ≤ 𝑝
4
5. Коэффициент ускорения (Speedup)
Введем обозначения:
o
𝑻(𝒏) – это время выполнения последовательной программы
(sequential program)
o
𝑻 𝒑 (𝒏) – это время выполнения параллельной программы
(parallel program) на p процессорах
Коэффициент S ускорения параллельной программ (Speedup):
𝑺𝒑 𝒏 =
𝑻(𝒏)
𝑻 𝒑 (𝒏)
Цель распараллеливания – достичь линейного ускорения
на максимально большом числе процессоров
𝑺 𝒑 𝒏 ≈ 𝒑 или
𝑺 𝒑 𝒏 = 𝑶(𝒑) при 𝒑 → ∞
5
6. Коэффициент ускорения (Speedup)
Какое время брать за 𝑻(𝒏) – за время выполнения
последовательной программы?
o Время лучшего известного алгоритма (в смысле вычислительной
сложности)?
o Время лучшего теоретически возможного алгоритма?
Что считать временем выполнения 𝑻 𝒑 𝒏 параллельной
программы?
o Среднее время выполнения потоков программы?
o Время выполнения потока, завершившего работу первым?
o Время выполнения потока, завершившего работу последним?
6
7. Коэффициент ускорения (Speedup)
Какое время брать за 𝑻(𝒏) – за время выполнения
последовательной программы?
o Время лучшего известного алгоритма или время алгоритма,
который подвергается распараллеливанию
Что считать временем выполнения 𝑻 𝒑 𝒏 параллельной
программы?
o Время выполнения потока, завершившего работу последним
7
8. Коэффициент ускорения (Speedup)
Коэффициент относительного ускорения (Relative speedup) –
отношения времени выполнения параллельной программы на одном
процессоре к времени её выполнения на p процессорах
𝑺 𝑹𝒆𝒍𝒂𝒕𝒊𝒗𝒆 𝒑, 𝒏 =
𝑻 𝟏 (𝒏)
𝑻 𝒑 (𝒏)
Коэффициент эффективности (Efficiency) параллельной программы
𝑬𝒑
𝑺 𝒑 (𝒏)
𝑻(𝒏)
𝒏 =
=
∈ [𝟎, 𝟏]
𝒏
𝒏𝑻 𝒑 (𝒏)
Коэффициент накладных расходов (Overhead)
𝑻 𝑺𝒚𝒏𝒄 (𝒑, 𝒏)
𝑻 𝑻𝒐𝒕𝒂𝒍 𝒑, 𝒏 − 𝑻 𝑪𝒐𝒎𝒑 (𝒑, 𝒏)
𝜺 𝒑, 𝒏 =
=
𝑻 𝑪𝒐𝒎𝒑 (𝒑, 𝒏)
𝑻 𝑪𝒐𝒎𝒑 (𝒑, 𝒏)
𝑻 𝑺𝒚𝒏𝒄 (𝒑, 𝒏) – время создания, синхронизации и взаимодействия p потоков
𝑻 𝑪𝒐𝒎𝒑 (𝒑, 𝒏) – время вычислений в каждом из p потоков
8
9. Коэффициент ускорения (Speedup)
35
S
Linear (ideal)
30
Speedup
25
N = 60 * 2^20
20
N = 40 * 2^20
15
10
N = 20 * 2^20
5
p
0
2
4
6
8
10
12
14
16
18
20
22
24
26
28
30
32
Processors
Зависимость коэффициента ускорения S
параллельного алгоритма X от количества p процессоров
Ускорение программы может расти с увеличением размера входных данных –
время вычислений превосходит накладные расходы на взаимодействия
потоков (управление потоками, синхронизацию, обмен сообщениями, …)
9
10. Коэффициент ускорения (Speedup)
35
S
Linear (ideal)
Линейное ускорение
30
Sp(n) = 4/5 * p = O(p)
N = 60 * 2^20
Speedup
25
Линейное ускорение
20
N = 40 * 2^20
Sp(n) = 1/5 * p = O(p)
15
10
N = 20 * 2^20
5
p
0
2
4
6
8
10
12
14
16
18
20
22
24
26
28
30
32
Processors
Зависимость коэффициента ускорения S
параллельного алгоритма X от количества p процессоров
Ускорение программы может расти с увеличением размера входных данных –
время вычислений превосходит накладные расходы на взаимодействия
потоков (управление потоками, синхронизацию, обмен сообщениями, …)
10
11. Коэффициент ускорения (Speedup)
Параллельная программа (алгоритм) коэффициент
ускорения, которой линейной растет с увеличением p
называется линейно масштабируемой или просто
масштабируемой (scalable)
Масштабируемая параллельная программа допускает
эффективную реализацию на различном числе
процессоров
11
12. Коэффициент ускорения (Speedup)
35
S
30
Sp(n) = log2p = O(log2p)
25
Speedup
Linear (ideal)
Логарифмическое ускорение
20
𝑺𝒑 𝒏 =
15
𝒑 = 𝑶( 𝒑)
10
5
p
0
2
4
6
8
10
12
14
16
18
20
22
24
26
28
30
32
Processors
Зависимость коэффициента ускорения S
параллельных алгоритмов Y и Z от количества p процессоров
12
13. Суперлинейное ускорение (Superlinear speedup)
Параллельная программа может характеризоваться суперлинейным
ускорением (Superlinear speedup) – коэффициент ускорения Sp(n) принимает
значение больше p
𝑺𝒑 𝒏 > 𝒑
Причина: иерархическая организация памяти:
Cache – RAM – Local disk (HDD/SSD) – Network storage
Последовательная программ выполняется на одном процессоре
и обрабатывает данные размера n
Параллельная программа имеет p потоков на p процессорах, каждый поток
работает со своей частью данных, большая часть которых может попасть
в кеш-память, в результате в каждом потоке сокращается время доступа
к данным
Тот же самый эффект можно наблюдать имя два уровня иерархической
памяти: диск-память
13
15. Закон Дж. Амдала (Amdahl’s law)
Пусть имеется последовательная программа c временем выполнения T(n)
Обозначим:
r
𝒓 ∈ [𝟎, 𝟏] – часть программы, которая может быть распараллелена
(perfectly parallelized)
𝒔 = 𝟏 − 𝒓 – часть программы, которая не может быть распараллелена
(purely sequential)
s
Закон Дж. Амдала (Gene Amdahl) [1]:
Максимальное ускорение Sp программы на p процессорах равняется
𝑺𝒑 =
𝑺∞ = lim 𝑆 𝑝 = lim
𝑝→∞
𝑝→∞
𝟏
𝟏− 𝒓 +
𝒓
𝒑
1
1
𝟏
=
=
𝑟
1− 𝑟
𝒔
1− 𝑟 + 𝑝
Amdahl Gene. Validity of the Single Processor Approach to Achieving Large-Scale Computing Capabilities //
AFIPS Conference Proceedings, 1967, pp. 483-485, http://www-inst.eecs.berkeley.edu/~n252/paper/Amdahl.pdf
15
16. Обоснование закона Дж. Амдала
Время выполнения последовательной программы есть 𝑇(𝑛)
Время выполнения параллельной программы на p процессорах
(время каждого потока) складывается из последовательной части s
и параллельной r:
𝑇𝑝
𝑇(𝑛)
𝑛 = 𝑇 𝑛 𝑠+
𝑟
𝑝
r
s
Вычислим значение коэффициент ускорения (по определению)
𝑆𝑝 𝑛 =
𝑇(𝑛)
=
𝑇𝑝 𝑛
𝑇(𝑛)
1
1
=
=
𝑟
𝑟
𝑇(𝑛)
𝑠+
(1 − 𝑟) +
𝑇 𝑛 𝑠+
𝑟
𝑝
𝑝
𝑝
16
17. Закон Дж. Амдала (Amdahl’s law)
25
S∞
20
15
10
5
r
0
0,1
0,2
0,3
0,4
0,5
0,6
0,7
0,8
0,9
0,95
Зависимость коэффициента S∞ ускорения
параллельной программы от доли r распараллеленного кода
17
18. Закон Дж. Амдала (Amdahl’s law)
Допущения закона Амдала
Последовательный алгоритм является наиболее оптимальным способом
решения задачи. Возможны ситуации когда параллельная программа
(алгоритм) эффективнее решает задачу (может эффективнее использовать
кеш-память, конвейер, SIMD-инструкции, …)
Время выполнения параллельной программы оценивается через время
выполнения последовательной, однако потоки параллельной программы
могут выполнятся эффективнее
𝑇𝑝
𝑇(𝑛)
𝑛 = 𝑇 𝑛 𝑠+
𝑟
𝑝
Ускорение Sp(n) оценивается для фиксированного размера n данных при
любых значениях p. На практике при увеличении числа используемых
процессоров размер n входных данных также увеличивают, так как может
быть доступно больше памяти
18
19. Закон Дж. Амдала (Amdahl’s law)
Следствие. Увеличение параллельной части r программ важнее,
чем увеличение количества процессоров
90
T, %
80
r = 30%
70
60
50
r = 60%
40
30
20
r = 90%
10
p
0
2
4
8
16
32
Зависимость времени Tp(n) выполнения параллельной программы
от количества p процессоров и доли r распараллеленного кода
(время в % от времени T1(n))
19
20. Закон Дж. Амдала (Amdahl’s law)
Следствие. Увеличение параллельной части r программ важнее,
чем увеличение количества процессоров
90
Удвоение числа процессоров
сокращает время
с 85% до 77,5%
T, %
80
r = 30%
70
60
50
r = 60%
40
30
20
r = 90%
10
Удвоение доли параллельного
0
кода сокращает время
4
с 85% до 70%2
p
8
16
32
Зависимость времени Tp(n) выполнения параллельной программы
от количества p процессоров и доли r распараллеленного кода
(время в % от времени T1(n))
20
21. Закон Густафсона-Барсиса
В 1980-х годах показали, что некоторые практические приложения могут
масштабироваться лучше (почти линейно), чем предсказывает закон Амдала!
Пусть имеется последовательная программа c временем выполнения T(n)
Обозначим 𝒔 ∈ [𝟎, 𝟏] – часть программы, которая не может быть распараллелена
(purely sequential)
Закон Густафсона-Барсиса (Gustafson–Barsis' law) [1]:
Масштабируемое ускорение Sp программы на p процессорах равняется
𝑺 𝒑 = 𝒑 − 𝒔(𝒑 − 𝟏)
Обоснование
Пусть a – время последовательной части, b – время параллельной части
𝑻 𝒑 𝒏 = 𝒂 + 𝒃,
𝐬 = 𝒂/(𝒂 + 𝒃),
𝑻 𝒏 = 𝒂 + 𝒑𝒃
𝑺 𝒑 𝒏 = 𝒔 + 𝒑 𝟏 − 𝒔 = 𝒑 − 𝒔(𝒑 − 𝟏)
Время выполнения последовательной программы выражается через время выполнения
параллельной
Reevaluating Amdahl's Law, John L. Gustafson, Communications of the ACM 31(5), 1988. pp. 532-533 //
http://www.scl.ameslab.gov/Publications/Gus/AmdahlsLaw/Amdahls.html
21
22. POSIX Threads
POSIX Threads – это стандарт (POSIX.1c Threads extensions
(IEEE Std 1003.1c-1995)), в котором определяется API
для создания и управления потоками
Библиотека pthread (pthread.h) ~ 100 функций
Thread management - creating, joining threads etc.
Mutexes
Condition variables
Synchronization between threads using read/write locks and
barriers
Семфафоры POSIX (префикс sem_) могут работать
с потоками pthread, но не являются частью стандарта
(определены в стандарте POSIX.1b, Real-time extensions
(IEEE Std 1003.1b-1993))
22
23. POSIX pthreads API
Всем типы данных и функции начинаются с префикса pthread_
Prefix
Functional group
pthread_
Threads themselves and miscellaneous
subroutines
pthread_attr_
Thread attributes objects
pthread_mutex_
Mutexes
pthread_mutexattr_
Mutex attributes objects.
pthread_cond_
Condition variables
pthread_condattr_
Condition attributes objects
pthread_key_
Thread-specific data keys
pthread_rwlock_
Read/write locks
pthread_barrier_
Synchronization barriers
https://computing.llnl.gov/tutorials/pthreads
23
24. POSIX pthreads API
Компиляция программы с поддержкой POSIX pthreads API
Compiler / Platform
Compiler Command
Description
Intel
GNU/Linux
icc -pthread
C
icpc -pthread
C++
PGI
GNU/Linux
pgcc -lpthread
C
pgCC -lpthread
C++
GNU GCC
GNU/Linux, Blue Gene
gcc -pthread
GNU C
g++ -pthread
GNU C++
IBM
Blue Gene
bgxlc_r
/
bgcc_r
bgxlC_r, bgxlc++_r
C (ANSI / non-ANSI)
C++
24
25. Создание потоков
int pthread_create(pthread_t *restrict thread,
const pthread_attr_t *restrict attr,
void *(*start_routine)(void*),
void *restrict arg);
Создает поток с заданными атрибутами attr и запускает в нем
функцию start_routine, передавая ей аргумент arg
Количество создаваемых в процессе потоков стандартом
не ограничивается и зависит от реализации
Размер стека потока можно задать через атрибут потока attr
Размер стека по умолчанию: getrlimit(RLIMIT_STRACK, &rlim);
$ ulimit –s
# The maximum stack size
8192
$ ulimit –u
# The maximum number of processes
1024
#
available to a single usere
25
26. Завершение потоков
Возврат (return) из стартовой функции (start_routine)
Вызов pthread_exit()
Вызов pthread_cancel() другим потоком
Процесс (и его потоки) завершаются вызовом exit()
26
27. Создание и завершение потоков
#include <pthread.h>
#define NTHREADS 5
void *thread_fun(void *threadid) {
long tid = (long)threadid;
printf("Hello from %ld!n", tid);
pthread_exit(NULL);
}
int main(int argc, char *argv[]) {
pthread_t threads[NTHREADS];
int rc; long t;
for (t = 0; t < NTHREADS; t++) {
rc = pthread_create(&threads[t], NULL, thread_fun, (void *)t);
if (rc) {
printf("ERROR %dn", rc);
exit(-1);
}
}
pthread_exit(NULL);
}
27
28. Создание и завершение потоков
#include <pthread.h> prog ./prog.c
$ gcc –pthread –o
#define NTHREADS 5
$ ./prog
void *thread_fun(void *threadid) {
Hello from 1!
long tid = (long)threadid;
Hello from 4! from %ld!n", tid);
printf("Hello
pthread_exit(NULL);
Hello from 0!
}
Hello from 2!
int main(int argc, char *argv[]) {
Hello from 3!
pthread_t threads[NTHREADS];
int rc; long t;
for (t = 0; t < NTHREADS; t++) {
rc = pthread_create(&threads[t], NULL, thread_fun, (void *)t);
if (rc) {
printf("ERROR %dn", rc);
exit(-1);
}
}
pthread_exit(NULL);
}
28
29. Ожидание потоков
Функция pthread_join() – позволяет дождаться завершения заданного
потока
Поток может быть типа “detached” или “joinable” (default)
К detached-потоку не применима функция pthread_join
(поток создается и существует независимо от других)
Joinable-поток требует хранения дополнительных данных
Тип потока можно задать через его атрибуты или вызвав функцию
pthread_detach
29
30. Ожидание потоков
#include <pthread.h>
#define NTHREADS 5
// ...
int main(int argc, char *argv[]) {
pthread_t threads[NTHREADS];
int rc; long t;
void *status;
for (t = 0; t < NTHREADS; t++) {
rc = pthread_create(&threads[t], NULL, thread_fun,
(void *)t);
}
for (t = 0; t < NTHREADS; t++) {
rc = pthread_join(threads[t], &status);
}
pthread_exit(NULL);
}
30
31. Синхронизация потоков
Функция pthread_self() – возвращает идентификатор потока
Функция pthread_equal() – позволяет сравнить идентификаторы двух
потоков
31
32. Взаимные исключения (mutex)
Mutex (mutual exclusion) – это объект синхронизации “взаимное
исключение”
Мьютексы используются для создания критических секций
(critical sections) – областей кода, которые выполняются в любой
момент времени только одним потоком
В критических секциях, как правило, содержится код работы
с разделяемыми переменными
pthread_mutex_init() – инициализирует мьютекс
pthread_mutex_destroy() – уничтожает мьютекс
pthread_mutex_lock() – блокирует выполнение потока,
пока он не захватит (acquire) мьютекс
pthread_mutex_trylock() – осуществляет попытку захватить мьютекс
pthread_mutex_unlock() – освобождает (release) мьютекс
32
35. pi.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
volatile long double pi = 0.0;
pthread_mutex_t piLock;
long double intervals;
int numThreads;
void *computePI(void *id)
{
long double x, width, localSum = 0;
int i, threadID = *((int*)id);
width = 1.0 / intervals;
for (i = threadID ; i < intervals; i += numThreads) {
x = (i + 0.5) * width;
localSum += 4.0 / (1.0 + x * x);
}
localSum *= width;
pthread_mutex_lock(&piLock);
pi += localSum;
pthread_mutex_unlock(&piLock);
return NULL;
}
35
36. pi.c (продолжение)
int main(int argc, char **argv)
{
pthread_t *threads;
void *retval;
int *threadID;
int i;
if (argc == 3) {
intervals = atoi(argv[1]);
numThreads = atoi(argv[2]);
threads = malloc(numThreads * sizeof(pthread_t));
threadID = malloc(numThreads * sizeof(int));
pthread_mutex_init(&piLock, NULL);
for (i = 0; i < numThreads; i++) {
threadID[i] = i;
pthread_create(&threads[i], NULL, computePI, threadID + i);
}
for (i = 0; i < numThreads; i++)
pthread_join(threads[i], &retval);
printf("Estimation of pi is %32.30Lf n", pi);
} else {
printf("Usage: ./a.out <numIntervals> <numThreads>n");
}
return 0;
}
36
37. Ссылки
Эхтер Ш., Робертс Дж. Многоядерное программирование. – СПб.: Питер,
2010. – 316 с.
Эндрюс Г.Р. Основы многопоточного, параллельного и распределенного
программирования. – М.: Вильямс, 2003. – 512 с.
Darryl Gove. Multicore Application Programming: for Windows, Linux, and
Oracle Solaris. – Addison-Wesley, 2010. – 480 p.
Maurice Herlihy, Nir Shavit. The Art of Multiprocessor Programming. –
Morgan Kaufmann, 2008. – 528 p.
Richard H. Carver, Kuo-Chung Tai. Modern Multithreading : Implementing,
Testing, and Debugging Multithreaded Java and C++/Pthreads/Win32
Programs. – Wiley-Interscience, 2005. – 480 p.
Anthony Williams. C++ Concurrency in Action: Practical Multithreading. –
Manning Publications, 2012. – 528 p.
Träff J.L. Introduction to Parallel Computing //
http://www.par.tuwien.ac.at/teach/WS12/ParComp.html
37