Лекция 7:
Многопоточное программирование
Часть 3
(Multithreading programming)
Курносов Михаил Георгиевич
к.т.н. доцент Каф...
Программный инструментарий
Прикладные библиотеки






Intel Threading Building Blocks (TBB)
Microsoft Concurrency Ru...
Стандарт OpenMP
 OpenMP (Open Multi-Processing) – стандарт, определяющий набор
директив компилятора, библиотечных процеду...
OpenMP
Compiler

Information

GNU GCC

Option: –fopenmp
gcc 4.2 – OpenMP 2.5,
gcc 4.4 – OpenMP 3.0,
gcc 4.7 – OpenMP 3.1
g...
Структура OpenMP-программы
 Программа представляется в виде
последовательных участков кода (serial code)
и параллельных р...
Пример OpenMP-программы
#include <omp.h>
int main()
{
#pragma omp parallel
{
printf("Thread %dn", omp_get_thread_num());
}...
Компиляция OpenMP-программы
$ gcc –fopenmp –o prog ./prog.c
$ ./prog
Thread 0
Thread 1
Thread 3
Thread 2

$ export OMP_NUM...
Пример OpenMP-программы
#include <omp.h>
int main()
{

#pragma omp parallel
{
#ifdef _OPENMP
printf("Thread %dn", omp_get_...
Директивы OpenMP
#pragma omp <директива> [раздел [ [,] раздел]...]

 Создание потоков
 Распределение вычислений между по...
Создание потоков (parallel)
#pragma omp parallel
{
/* Этот код выполняется всеми потоками */
}
#pragma omp parallel if (ex...
Создание потоков (sections)
#pragma omp parallel sections
{
#pragma omp section
{
/* Код потока 0 */
}
#pragma omp section...
Функции runtime-библиотеки
 int omp_get_thread_num() – возвращает номер текущего
потока
 int omp_get_num_threads() – воз...
Директива master
#pragma omp parallel
{
/* Этот код выполняется всеми потоками */
#pragma omp master
{
/* Код выполняется ...
Директива single
#pragma omp parallel
{
/* Этот код выполняется всеми потоками */
#pragma omp single
{
/* Код выполняется ...
Директива for (data parallelism)
#define N 13
#pragma omp parallel
{
#pragma omp for
for (i = 0; i < N; i++) {
printf("Thr...
Директива for
$ OMP_NUM_THREADS=4 ./prog
Thread 2 i = 7
Thread 2 i = 8
Thread 2 i = 9
Thread 0 i = 0
Thread 0 i = 1
Thread...
Алгоритмы распределения итераций
#define N 13
#pragma omp parallel
{
#pragma omp for schedule(static, 2)
for (i = 0; i < N...
Алгоритмы распределения итераций
$ OMP_NUM_THREADS=4 ./prog
Thread 0 i = 0
Thread 0 i = 1
Thread 0 i = 8
Thread 0 i = 9
Th...
Алгоритмы распределения итераций
Алгоритм
static, m

Описание
Цикл делится на блоки по m итераций
(до выполнения), которые...
Директива for (ordered)
#define N 7
#pragma omp parallel
{
#pragma omp for ordered
for (i = 0; i < N; i++) {
#pragma omp o...
Директива for (ordered)
$ OMP_NUM_THREADS=4 ./prog
Thread 0 i = 0
Thread 0 i = 1
Thread 1 i = 2
Thread 1 i = 3
Thread 2 i ...
Директива for (nowait)
#define N 7
#pragma omp parallel
{
#pragma omp for nowait
for (i = 0; i < N; i++) {
printf("Thread ...
Директива for (collapse)
#define N 3
#define M 4
#pragma omp parallel
{
#pragma omp for collapse(2)
for (i = 0; i < N; i++...
Директива for (collapse)
$ OMP_NUM_THREADS=4 ./prog
Thread 2 i = 1
Thread 2 i = 1
Thread 2 i = 2
Thread 0 i = 0
Thread 0 i...
Ошибки в многопоточных программах
#include <iostream>
#include <vector>
int main()
{
std::vector<int> vec(1000);

std::fil...
Ошибки в многопоточных программах
$ g++ –fopenmp –o ompprog ./ompprog.cpp
$ ./omprog
Counter = 381
$ ./omprog
Counter = 90...
Ошибки в многопоточных программах
#include <iostream>
#include <vector>
int main()
{
std::vector<int> vec(1000);

Потоки о...
Состояние гонки (Race condition, data race)
#pragma omp parallel
{
counter++;
}

movl [counter], %eax
incl %eax
movl %eax,...
Состояние гонки (Race condition, data race)
#pragma omp parallel
{
counter++;
}

movl [counter], %eax
incl %eax
movl %eax,...
Состояние гонки (Race condition, data race)
 Состояние гонки (Race condition, data race) –
это состояние программы, в кот...
Обнаружение состояния гонки (Data race)
Динамические анализаторы
 Valgrind Helgrind, DRD
 Intel Thread Checker
 Oracle ...
Valgrind Helgrind
$ g++ -fopenmp -o ompprog ./ompprog.cpp
$ valgrind --helgrind ./ompprog
==8238==
==8238==
==8238==
==823...
Директивы синхронизации


Директивы синхронизации позволяют управлять
порядком выполнения заданных участков кода потоками...
Критические секции
#pragma omp parallel for private(v)
for (i = 0; i < n; i++) {
v = fun(a[i]);
#pragma omp critical
{
sum...
Критические секции
#pragma omp parallel for private(v)
for (i = 0; i < n; i++) {
v = fun(a[i]);
#pragma omp critical
{
sum...
Управление видимостью переменных
 private(list) – во всех потоках создаются локальные копии
переменных (начальное значени...
Атомарные операции
#pragma omp parallel for private(v)
for (i = 0; i < n; i++) {
v = fun(a[i]);
#pragma omp atomic
sum += ...
Атомарные операции
#pragma omp parallel for private(v)
for (i = 0; i < n; i++) {
v = fun(a[i]);
#pragma omp atomic
sum += ...
Параллельная редукция
#pragma omp parallel for reduction(+:sum)
for (i = 0; i < n; i++) {
sum = sum + fun(a[i]);
}

 Опер...
Директивы синхронизации
#pragma omp
{
/* Code
#pragma
/* Code
}

parallel

*/
omp barrier
*/

 Директива barrier осуществ...
#pragma omp flush
#pragma omp
{
/* Code
#pragma
/* Code
}

parallel
*/
omp flush(a, b)
*/



Принудительно обновляет в па...
Умножение матриц v1.0
#pragma omp parallel
{
#pragma omp for
for (i = 0; i < N; i++) {
for (j = 0; j < N; j++) {
for (k = ...
Умножение матриц v1.0
#pragma omp parallel
{
#pragma omp for
for (i = 0; i < N; i++) {
for (j = 0; j < N; j++) {
for (k = ...
Умножение матриц v2.0
#pragma omp parallel
{
#pragma omp for shared(a, b, c) private(j, k)
for (i = 0; i < N; i++) {
for (...
Директива task (OpenMP 3.0)
int fib(int n)
{
if (n < 2)
return n;
return fib(n - 1) + fib(n - 2);
}


Директива task созд...
Директива task (OpenMP 3.0)
int fib(int n)
{
int x, y;
if (n < 2)
return n;
#pragma omp task shared(x, n)
x = fib(n - 1);
...
Пример Primes (sequential code)
start = atoi(argv[1]);
end = atoi(argv[2]);
if ((start % 2) == 0 )
start = start + 1;

npr...
Пример Primes (serial code)
int is_prime_number(int num)
{
int limit, factor = 3;
limit = (int)(sqrtf((double)num) + 0.5f)...
Пример Primes (parallel code)
start = atoi(argv[1]);
end = atoi(argv[2]);
if ((start % 2) == 0 )
start = start + 1;

nprim...
Пример Primes (parallel code)
start = atoi(argv[1]);
end = atoi(argv[2]);
if ((start % 2) == 0 )
start = start + 1;

nprim...
Пример Primes (parallel code)
start = atoi(argv[1]);
end = atoi(argv[2]);
if ((start % 2) == 0 )
start = start + 1;

nprim...
Пример Primes (parallel code)
start = atoi(argv[1]);
end = atoi(argv[2]);
if ((start % 2) == 0 )
start = start + 1;

nprim...
Пример Primes (parallel code)
start = atoi(argv[1]);
end = atoi(argv[2]);
if ((start % 2) == 0 )
start = start + 1;

nprim...
Пример Primes (parallel code)
start = atoi(argv[1]);
end = atoi(argv[2]);
if ((start % 2) == 0 )
start = start + 1;

nprim...
Пример Primes (parallel code)
#pragma omp parallel for reduction(+:nprimes)
for (i = start; i <= end; i += 2) {
if (is_pri...
Пример Primes (parallel code)
start = atoi(argv[1]);
end = atoi(argv[2]);
if ((start % 2) == 0 )
start = start + 1;

nprim...
Блокировки (locks)
 Блокировка, мьютекс (lock, mutex) – это объект синхронизации,
который позволяет ограничить одновремен...
Spin locks (циклические блокировки)
 Spin lock (блокировка в цикле) – это вид блокировки, при который
процесс ожидающий о...
Блокировки чтения-записи (rwlocks)
 Read-write lock – это вид блокировки, которая позволяет
разграничить доступ потоков н...
Блокировки (locks)
#include <omp.h>

int main()
{
std::vector<int> vec(1000);
std::fill(vec.begin(), vec.end(), 1);
int co...
Взаимная блокировка (Deadlock)
 Взаимная блокировка (deadlock, тупик) –
ситуация когда два и более потока находятся в сос...
Взаимная блокировка (Deadlock)
void deadlock_example()
{
#pragma omp sections
{
#pragma omp section
{
omp_lock_t lock1, lo...
OpenMP 4.0: Поддержка ускорителей (GPU)
sum = 0;
#pragma omp target device(acc0) in(B,C)
#pragma omp parallel for reductio...
OpenMP 4.0: SIMD-конструкции
 SIMD-конструкции для векторизации циклов
(SSE, AVX2, AVX-512, AltiVec, …)
void minex(float ...
OpenMP 4.0: Thread Affinity
 Thread affinity – привязка потоков к процессорным ядрам


#pragma omp parallel proc_bind(ma...
OpenMP 4.0: user defined reductions
#pragma omp declare reduction (reduction-identifier :
typename-list : combiner) [ident...
POSIX Threads
 POSIX Threads – это стандарт (POSIX.1c Threads extensions
(IEEE Std 1003.1c-1995)), в котором определяется...
POSIX pthreads API
 Всем типы данных и функции начинаются с префикса pthread_
Prefix

Functional group

pthread_

Threads...
POSIX pthreads API
 Компиляция программы с поддержкой POSIX pthreads API
Compiler / Platform

Compiler Command

Descripti...
Создание потоков
int pthread_create(pthread_t *restrict thread,
const pthread_attr_t *restrict attr,
void *(*start_routine...
Завершение потоков
 Возврат (return) из стартовой функции (start_routine)
 Вызов pthread_exit()

 Вызов pthread_cancel(...
Создание и завершение потоков
#include <pthread.h>
#define NTHREADS 5
void *thread_fun(void *threadid) {
long tid = (long)...
Создание и завершение потоков
#include <pthread.h> prog ./prog.c
$ gcc –pthread –o
#define NTHREADS 5
$ ./prog
void *threa...
Ожидание потоков
 Функция pthread_join() – позволяет дождаться завершения заданного
потока
 Поток может быть типа “detac...
Ожидание потоков
#include <pthread.h>
#define NTHREADS 5
// ...
int main(int argc, char *argv[]) {
pthread_t threads[NTHRE...
Синхронизация потоков
 Функция pthread_self() – возвращает идентификатор потока
 Функция pthread_equal() – позволяет сра...
Взаимные исключения (mutex)
 Mutex (mutual exclusion) – это объект синхронизации “взаимное
исключение”
 Мьютексы использ...
Взаимные исключения (mutex)
node_t *llist_delete(int value)
{
node_t *prev, *current;
prev = &head;
pthread_mutex_lock(&pr...
Вычисление числа π
 Приближенное вычисление числа π
1

𝜋=
0

4
2 𝑑𝑥
1+ 𝑥

𝑛

𝜋≈ℎ
𝑖=1

4
1 + (ℎ(𝑖 − 0.5))2

1
ℎ=
𝑛

4,50

...
pi.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
volatile long double pi = 0.0;
pthread_mutex_t piLock;
lo...
pi.c (продолжение)
int main(int argc, char **argv)
{
pthread_t *threads;
void *retval;
int *threadID;
int i;
if (argc == 3...
Windows API для многопоточной обработки


Win32 API Threads CreateThread – системный вызов

HANDLE WINAPI CreateThread(
L...
Windows API для многопоточной обработки
 .NET System.Threading
public sealed class Thread : CriticalFinalizerObject, _Thr...
C11 threads
#include <threads.h>

void threadfun()
{
printf("Hello from threadn");
}
int main()
{
thrd_t tid;
int rc;
rc =...
Ссылки


Эхтер Ш., Робертс Дж. Многоядерное программирование. – СПб.: Питер,
2010. – 316 с.



Эндрюс Г.Р. Основы многоп...
Upcoming SlideShare
Loading in...5
×

Лекция 7: Фибоначчиевы кучи (Fibonacci heaps)

234

Published on

0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total Views
234
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
5
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

Лекция 7: Фибоначчиевы кучи (Fibonacci heaps)

  1. 1. Лекция 7: Многопоточное программирование Часть 3 (Multithreading programming) Курносов Михаил Георгиевич к.т.н. доцент Кафедры вычислительных систем Сибирский государственный университет телекоммуникаций и информатики http://www.mkurnosov.net
  2. 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. 3. Стандарт OpenMP  OpenMP (Open Multi-Processing) – стандарт, определяющий набор директив компилятора, библиотечных процедур и переменных среды окружения для создания многопоточных программ  Текущая версия стандарта – OpenMP 4.0  Требуется поддержка со стороны компилятора www.openmp.org 3
  4. 4. OpenMP Compiler Information GNU GCC Option: –fopenmp gcc 4.2 – OpenMP 2.5, gcc 4.4 – OpenMP 3.0, gcc 4.7 – OpenMP 3.1 gcc 4.9 – OpenMP 4.0 Clang (LLVM) OpenMP 3.1 Clang + Intel OpenMP RTL http://clang-omp.github.io/ Intel C/C++, Fortran OpenMP 3.1 Option: –Qopenmp, –openmp Oracle Solaris Studio C/C++/Fortran OpenMP 3.1 Option: –xopenmp Microsoft Visual Studio 2012 C++ Option: /openmp OpenMP 2.0 only Other compilers: IBM XL, PathScale, PGI, Absoft Pro, … http://openmp.org/wp/openmp-compilers/ 4
  5. 5. Структура OpenMP-программы  Программа представляется в виде последовательных участков кода (serial code) и параллельных регионов (parallel region)  Каждый поток имеет номер: 0, 1, 2, … 0 0 1 2  Главный поток (master) имеет номер 0  Память процесса (heap) является общей для всех потоков Барьерная синхронизация  OpenMP реализует динамическое управление потоками (task parallelism)  OpenMP: data parallelism + task parallelism 0 0 1 5
  6. 6. Пример OpenMP-программы #include <omp.h> int main() { #pragma omp parallel { printf("Thread %dn", omp_get_thread_num()); } return 0; } 6
  7. 7. Компиляция OpenMP-программы $ gcc –fopenmp –o prog ./prog.c $ ./prog Thread 0 Thread 1 Thread 3 Thread 2 $ export OMP_NUM_THREADS=2 $ ./prog Thread 0 Thread 1 По умолчанию количество потоков = количеству логических процессоров в системе 7
  8. 8. Пример OpenMP-программы #include <omp.h> int main() { #pragma omp parallel { #ifdef _OPENMP printf("Thread %dn", omp_get_thread_num()); #endif } return 0; } 8
  9. 9. Директивы OpenMP #pragma omp <директива> [раздел [ [,] раздел]...]  Создание потоков  Распределение вычислений между потоками  Управление пространством видимости переменных  Механизмы синхронизации потоков  … 9
  10. 10. Создание потоков (parallel) #pragma omp parallel { /* Этот код выполняется всеми потоками */ } #pragma omp parallel if (expr) { /* Код выполняется потоками если expr = true */ } #pragma omp parallel num_threads(n / 2) { /* Создается n / 2 потоков */ } На выходе из параллельного региона осуществляется барьерная синхронизация – все потоки ждут последнего 10
  11. 11. Создание потоков (sections) #pragma omp parallel sections { #pragma omp section { /* Код потока 0 */ } #pragma omp section { /* Код потока 1 */ } } При любых условиях выполняется фиксированное количество потоков (по количеству секций) 11
  12. 12. Функции runtime-библиотеки  int omp_get_thread_num() – возвращает номер текущего потока  int omp_get_num_threads() – возвращает количество потоков в параллельном регионе  void omp_set_num_threads(int n)  double omp_get_wtime() 12
  13. 13. Директива master #pragma omp parallel { /* Этот код выполняется всеми потоками */ #pragma omp master { /* Код выполняется только потоком 0 */ } /* Этот код выполняется всеми потоками */ } 13
  14. 14. Директива single #pragma omp parallel { /* Этот код выполняется всеми потоками */ #pragma omp single { /* Код выполняется только одним потоком */ } /* Этот код выполняется всеми потоками */ } 14
  15. 15. Директива for (data parallelism) #define N 13 #pragma omp parallel { #pragma omp for for (i = 0; i < N; i++) { printf("Thread %d i = %dn", omp_get_thread_num(), i); } } Итерации цикла распределяются между потоками i: 0 1 2 Thread 0 3 4 5 Thread 1 6 7 8 Thread 2 9 10 11 12 Thread 3 15
  16. 16. Директива for $ OMP_NUM_THREADS=4 ./prog Thread 2 i = 7 Thread 2 i = 8 Thread 2 i = 9 Thread 0 i = 0 Thread 0 i = 1 Thread 0 i = 2 Thread 3 i = 10 Thread 3 i = 11 Thread 3 i = 12 Thread 0 i = 3 Thread 1 i = 4 Thread 1 i = 5 Thread 1 i = 6 16
  17. 17. Алгоритмы распределения итераций #define N 13 #pragma omp parallel { #pragma omp for schedule(static, 2) for (i = 0; i < N; i++) { printf("Thread %d i = %dn", omp_get_thread_num(), i); } } Итерации цикла распределяются циклически (round-robin) блоками по 2 итерации i: 0 1 T0 2 3 T1 4 5 T2 6 7 T3 8 9 T0 10 11 T1 12 T2 17
  18. 18. Алгоритмы распределения итераций $ OMP_NUM_THREADS=4 ./prog Thread 0 i = 0 Thread 0 i = 1 Thread 0 i = 8 Thread 0 i = 9 Thread 1 i = 2 Thread 1 i = 3 Thread 1 i = 10 Thread 1 i = 11 Thread 3 i = 6 Thread 3 i = 7 Thread 2 i = 4 Thread 2 i = 5 Thread 2 i = 12 18
  19. 19. Алгоритмы распределения итераций Алгоритм static, m Описание Цикл делится на блоки по m итераций (до выполнения), которые распределяются по потокам Цикл делится на блоки по m итераций. dynamic, m При выполнении блока из m итераций поток выбирает следующий блок из общего пула guided, m runtime Блоки выделяются динамически. При каждом запросе размер блока уменьшается экспоненциально до m Алгоритм задается пользователем через переменную среды OMP_SCHEDULE 19
  20. 20. Директива for (ordered) #define N 7 #pragma omp parallel { #pragma omp for ordered for (i = 0; i < N; i++) { #pragma omp ordered printf("Thread %d i = %dn", omp_get_thread_num(), i); } }  Директива ordered организует последовательное выполнение итераций (i = 0, 1, …) – синхронизация  Поток с i = k ожидает пока потоки с i = k – 1, k – 2, … не выполнят свои итерации 20
  21. 21. Директива for (ordered) $ OMP_NUM_THREADS=4 ./prog Thread 0 i = 0 Thread 0 i = 1 Thread 1 i = 2 Thread 1 i = 3 Thread 2 i = 4 Thread 2 i = 5 Thread 3 i = 6 21
  22. 22. Директива for (nowait) #define N 7 #pragma omp parallel { #pragma omp for nowait for (i = 0; i < N; i++) { printf("Thread %d i = %dn", omp_get_thread_num(), i); } }  По окончанию цикла потоки не выполняют барьерную синхронизацию  Конструкция nowait применима и к директиве sections 22
  23. 23. Директива for (collapse) #define N 3 #define M 4 #pragma omp parallel { #pragma omp for collapse(2) for (i = 0; i < N; i++) { for (j = 0; j < M; j++) printf("Thread %d i = %dn", omp_get_thread_num(), i); } }  сollapse(n) объединяет пространство итераций n циклов в одно i: 0 0,0 1 0,1 T0 + 2 0,2 j: 0 0,3 1,0 T1 1 1,1 2 3 1,2 1,3 T2 2,0 2,1 2,2 T3 2,3 23
  24. 24. Директива for (collapse) $ OMP_NUM_THREADS=4 ./prog Thread 2 i = 1 Thread 2 i = 1 Thread 2 i = 2 Thread 0 i = 0 Thread 0 i = 0 Thread 0 i = 0 Thread 3 i = 2 Thread 3 i = 2 Thread 3 i = 2 Thread 1 i = 0 Thread 1 i = 1 Thread 1 i = 1 24
  25. 25. Ошибки в многопоточных программах #include <iostream> #include <vector> int main() { std::vector<int> vec(1000); std::fill(vec.begin(), vec.end(), 1); int counter = 0; #pragma omp parallel for for (std::vector<int>::size_type i = 0; i < vec.size(); i++) { if (vec[i] > 0) { counter++; } } std::cout << "Counter = " << counter << std::endl; return 0; } 25
  26. 26. Ошибки в многопоточных программах $ g++ –fopenmp –o ompprog ./ompprog.cpp $ ./omprog Counter = 381 $ ./omprog Counter = 909 $ ./omprog Counter = 398 На каждом запуске итоговое значение Counter разное! Правильный результат Counter = 1000 26
  27. 27. Ошибки в многопоточных программах #include <iostream> #include <vector> int main() { std::vector<int> vec(1000); Потоки осуществляют конкурентный std::fill(vec.begin(), vec.end(), 1); int counter = 0; доступ к переменной counter – одновременно читают её и записывают #pragma omp parallel for for (std::vector<int>::size_type i = 0; i < vec.size(); i++) { if (vec[i] > 0) { counter++; } } std::cout << "Counter = " << counter << std::endl; return 0; } 27
  28. 28. Состояние гонки (Race condition, data race) #pragma omp parallel { counter++; } movl [counter], %eax incl %eax movl %eax, [counter] C++ Идеальная последовательность выполнения инструкций 2-х потоков Thread 0 Memory (counter) Thread 1 0 movl [counter], %eax ← 0 0 incl %eax → movl %eax, [counter] movl [counter], %eax 1 ← 1 1 incl %eax movl %eax, [counter] counter = 2 → 2 28
  29. 29. Состояние гонки (Race condition, data race) #pragma omp parallel { counter++; } movl [counter], %eax incl %eax movl %eax, [counter] C++ Возможная последовательность выполнения инструкций 2-х потоков Thread 0 Memory (counter) Thread 1 0 ← movl [counter], %eax 0 incl %eax movl [counter], %eax ← 0 movl %eax, [counter] incl %eax → 1 movl %eax, [counter] → 1 1 Error: Data race counter = 1 29
  30. 30. Состояние гонки (Race condition, data race)  Состояние гонки (Race condition, data race) – это состояние программы, в которой несколько потоков одновременно конкурируют за доступ к общей структуре данных (для чтения/записи)  Порядок выполнения потоков заранее не известен – носит случайный характер  Планировщик динамически распределяет процессорное время учитывая текущую загруженность процессорных ядер, а нагрузку (потоки, процессы) создают пользователи, поведение которых носит случайных характер  Состояние гонки данных (Race condition, data race) трудно обнаруживается в программах и воспроизводится в тестах  Состояние гонки данных (Race condition, data race) – это типичный пример Гейзенбага (Heisenbug) 30
  31. 31. Обнаружение состояния гонки (Data race) Динамические анализаторы  Valgrind Helgrind, DRD  Intel Thread Checker  Oracle Studio Thread Analyzer  Java ThreadSanitizer  Java Chord Статические анализаторы кода  PVS-Studio (viva64)  … 31
  32. 32. Valgrind Helgrind $ g++ -fopenmp -o ompprog ./ompprog.cpp $ valgrind --helgrind ./ompprog ==8238== ==8238== ==8238== ==8238== ... ==8266== Helgrind, a thread error detector Copyright (C) 2007-2012, and GNU GPL'd, by OpenWorks LLP et al. Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info Command: ./ompprog report ---------------------------------------------------------------- ==8266== Possible data race during write of size 4 at 0x7FEFFD358 by thread #3 ==8266== Locks held: none ==8266== at 0x400E6E: main._omp_fn.0 (ompprog.cpp:14) ==8266== by 0x3F84A08389: ??? (in /usr/lib64/libgomp.so.1.0.0) ==8266== by 0x4A0A245: ??? (in /usr/lib64/valgrind/vgpreload_helgrind-amd64-linux.so) ==8266== by 0x34CFA07C52: start_thread (in /usr/lib64/libpthread-2.17.so) ==8266== by 0x34CF2F5E1C: clone (in /usr/lib64/libc-2.17.so) ==8266== ==8266== This conflicts with a previous write of size 4 by thread #1 ==8266== Locks held: none ==8266== ==8266== at 0x400E6E: main._omp_fn.0 (ompprog.cpp:14) by 0x400CE8: main (ompprog.cpp:11)... 32
  33. 33. Директивы синхронизации  Директивы синхронизации позволяют управлять порядком выполнения заданных участков кода потоками  #pragma omp critical  #pragma omp atomic  #pragma omp ordered  #pragma omp barrier 33
  34. 34. Критические секции #pragma omp parallel for private(v) for (i = 0; i < n; i++) { v = fun(a[i]); #pragma omp critical { sum += v; } } 34
  35. 35. Критические секции #pragma omp parallel for private(v) for (i = 0; i < n; i++) { v = fun(a[i]); #pragma omp critical { sum += v; } }  Критическая секция (Critical section) – участок кода в многопоточной программе, выполняемый всеми потоками последовательно  Критические секции снижают степень параллелизма 35
  36. 36. Управление видимостью переменных  private(list) – во всех потоках создаются локальные копии переменных (начальное значение)  firstprivate(list) – во всех потоках создаются локальные копии переменных, которые инициализируются их значениями до входа в параллельный регион  lastprivate(list) – во всех потоках создаются локальные копии переменных. По окончанию работы всех потоков локальная переменная вне параллельного региона обновляется значением этой переменной одного из потоков  shared(list) – переменные являются общими для всех потоков 36
  37. 37. Атомарные операции #pragma omp parallel for private(v) for (i = 0; i < n; i++) { v = fun(a[i]); #pragma omp atomic sum += v; } 37
  38. 38. Атомарные операции #pragma omp parallel for private(v) for (i = 0; i < n; i++) { v = fun(a[i]); #pragma omp atomic sum += v; }  Атомарные операции “легче” критических секций (не используют блокировки)  Lock-free algorithms & data structures 38
  39. 39. Параллельная редукция #pragma omp parallel for reduction(+:sum) for (i = 0; i < n; i++) { sum = sum + fun(a[i]); }  Операции директивы reduction: +, *, -, &, |, ^, &&, ||, max, min  OpenMP 4.0 поддерживает пользовательские функции редукции 39
  40. 40. Директивы синхронизации #pragma omp { /* Code #pragma /* Code } parallel */ omp barrier */  Директива barrier осуществляет ожидание достижения данной точки программы всеми потоками 40
  41. 41. #pragma omp flush #pragma omp { /* Code #pragma /* Code } parallel */ omp flush(a, b) */  Принудительно обновляет в памяти значения переменных (Memory barrier)  Например, в одном потоке выставляем флаг (сигнал к действию) для другого 41
  42. 42. Умножение матриц v1.0 #pragma omp parallel { #pragma omp for for (i = 0; i < N; i++) { for (j = 0; j < N; j++) { for (k = 0; k < N; k++) { c[i][j] = c[i][j] + a[i][k] * b[k][j]; } } } } 42
  43. 43. Умножение матриц v1.0 #pragma omp parallel { #pragma omp for for (i = 0; i < N; i++) { for (j = 0; j < N; j++) { for (k = 0; k < N; k++) { c[i][j] = c[i][j] + a[i][k] * b[k][j]; } } } } Ошибка! Переменные j, k – общие для всех потоков! 43
  44. 44. Умножение матриц v2.0 #pragma omp parallel { #pragma omp for shared(a, b, c) private(j, k) for (i = 0; i < N; i++) { for (j = 0; j < N; j++) { for (k = 0; k < N; k++) { c[i][j] = c[i][j] + a[i][k] * b[k][j]; } } } } 44
  45. 45. Директива task (OpenMP 3.0) int fib(int n) { if (n < 2) return n; return fib(n - 1) + fib(n - 2); }  Директива task создает задачу (легковесный поток)  Задачи из пула динамически выполняются группой потоков  Динамическое распределение задача по потокам осуществляется алгоритмами планирования типа work stealing  Задач может быть намного больше количества потоков 45
  46. 46. Директива task (OpenMP 3.0) int fib(int n) { int x, y; if (n < 2) return n; #pragma omp task shared(x, n) x = fib(n - 1); #pragma omp task shared(y, n) y = fib(n - 2); #pragma omp taskwait return x + y; } #pragma omp parallel #pragma omp single val = fib(n); Каждый рекурсивный вызов – это задача Ожидаем завершение дочерних задач 46
  47. 47. Пример Primes (sequential code) start = atoi(argv[1]); end = atoi(argv[2]); if ((start % 2) == 0 ) start = start + 1; nprimes = 0; if (start <= 2) nprimes++; for (i = start; i <= end; i += 2) { if (is_prime_number(i)) nprimes++; }  Программа подсчитывает количество простых чисел в интервале [start, end] 47
  48. 48. Пример Primes (serial code) int is_prime_number(int num) { int limit, factor = 3; limit = (int)(sqrtf((double)num) + 0.5f); while ((factor <= limit) && (num % factor)) factor++; return (factor > limit); } 48
  49. 49. Пример Primes (parallel code) start = atoi(argv[1]); end = atoi(argv[2]); if ((start % 2) == 0 ) start = start + 1; nprimes = 0; if (start <= 2) nprimes++; #pragma omp parallel for for (i = start; i <= end; i += 2) { if (is_prime_number(i)) nprimes++; } 49
  50. 50. Пример Primes (parallel code) start = atoi(argv[1]); end = atoi(argv[2]); if ((start % 2) == 0 ) start = start + 1; nprimes = 0; if (start <= 2) nprimes++; #pragma omp parallel for for (i = start; i <= end; i += 2) { if (is_prime_number(i)) nprimes++; Data } race 50
  51. 51. Пример Primes (parallel code) start = atoi(argv[1]); end = atoi(argv[2]); if ((start % 2) == 0 ) start = start + 1; nprimes = 0; if (start <= 2) nprimes++; #pragma omp parallel for for (i = start; i <= end; i += 2) { if (is_prime_number(i)) #pragma omp critical nprimes++; } 51
  52. 52. Пример Primes (parallel code) start = atoi(argv[1]); end = atoi(argv[2]); if ((start % 2) == 0 ) start = start + 1; nprimes = 0; if (start <= 2) nprimes++; #pragma omp parallel for for (i = start; i <= end; i += 2) { if (is_prime_number(i)) Увеличение счетчика можно #pragma omp critical реализовать без блокировки nprimes++; (Lock-free algorithm) } 52
  53. 53. Пример Primes (parallel code) start = atoi(argv[1]); end = atoi(argv[2]); if ((start % 2) == 0 ) start = start + 1; nprimes = 0; if (start <= 2) nprimes++; #pragma omp parallel for reduction(+:nprimes) for (i = start; i <= end; i += 2) { if (is_prime_number(i)) nprimes++; } 53
  54. 54. Пример Primes (parallel code) start = atoi(argv[1]); end = atoi(argv[2]); if ((start % 2) == 0 ) start = start + 1; nprimes = 0; if (start <= 2) nprimes++; #pragma omp parallel for reduction(+:nprimes) for (i = start; i <= end; i += 2) { Время выполнения if (is_prime_number(i)) is_prime_number(i) nprimes++; зависит от значения i } 54
  55. 55. Пример Primes (parallel code) #pragma omp parallel for reduction(+:nprimes) for (i = start; i <= end; i += 2) { if (is_prime_number(i)) nprimes++; } Поток 0 Поток 1 Поток 2 Время выполнения потоков различно! Load Imbalance Поток 3 Поток 4 55
  56. 56. Пример Primes (parallel code) start = atoi(argv[1]); end = atoi(argv[2]); if ((start % 2) == 0 ) start = start + 1; nprimes = 0; if (start <= 2) nprimes++; #pragma omp parallel for schedule(static, 1) reduction(+:nprimes) for (i = start; i <= end; i += 2) { if (is_prime_number(i)) nprimes++; } 56
  57. 57. Блокировки (locks)  Блокировка, мьютекс (lock, mutex) – это объект синхронизации, который позволяет ограничить одновременный доступ потоков к разделяемым ресурсам (реализует взаимное исключение)     OpenMP: omp_lock_set/omp_lock_unset POSIX Pthreads: pthread_mutex_lock/pthread_mutex_unlock C++11: std::mutex::lock/std::mutex::unlock C11: mtx_lock/mtx_unlock  Блокировка (lock) может быть рекурсивной (вложенной) – один поток может захватывать блокировку несколько раз 57
  58. 58. Spin locks (циклические блокировки)  Spin lock (блокировка в цикле) – это вид блокировки, при который процесс ожидающий освобождения блокировки не “засыпает”, а выполняет цикл ожидания (busy waiting)  Рекомендуется использовать если время пребывания в критической секции меньше времени переключения контекстов  Плюсы: spin lock позволяет быстро среагировать на освобождение блокировки  Минусы: spin lock всегда занимает ресурсы процессорного ядра pthread_spinlock_t lock; pthread_spin_init(&lock, PTHREAD_PROCESS_PRIVATE); pthread_spin_lock(&lock); counter++; pthread_spin_unlock(&lock); pthread_spin_destroy(&lock); 58
  59. 59. Блокировки чтения-записи (rwlocks)  Read-write lock – это вид блокировки, которая позволяет разграничить доступ потоков на запись и чтение разделяемых структур данных  Блокировка на запись не может быть получена, пока не освобождены все блокировки на чтение (rdlock) int readData(int i, int j) { pthread_rwlock_rdlock(&lock); int result = data[i] + data[j]; pthread_rwlock_unlock(&lock); return result; } void writeData(int i, int j, int value) { pthread_rwlock_wrlock(&lock); data[i] += value; data[j] -= value; pthread_rwlock_unlock(&lock); } 59
  60. 60. Блокировки (locks) #include <omp.h> int main() { std::vector<int> vec(1000); std::fill(vec.begin(), vec.end(), 1); int counter = 0; omp_lock_t lock; omp_init_lock(&lock); #pragma omp parallel for for (std::vector<int>::size_type i = 0; i < vec.size(); i++) { if (vec[i] > 0) { omp_set_lock(&lock); counter++; omp_unset_lock(&lock); } } omp_destroy_lock(&lock); std::cout << "Counter = " << counter << std::endl; return 0; } 60
  61. 61. Взаимная блокировка (Deadlock)  Взаимная блокировка (deadlock, тупик) – ситуация когда два и более потока находятся в состоянии бесконечного ожидания ресурсов, захваченных этими потоками  Самоблокировка (self deadlock) – ситуация когда поток пытается повторно захватить блокировку, которую уже захватил (deadlock возникает если блокировка не является рекурсивной) 61
  62. 62. Взаимная блокировка (Deadlock) void deadlock_example() { #pragma omp sections { #pragma omp section { omp_lock_t lock1, lock2; omp_set_lock(&lock1); omp_set_lock(&lock2); // Code omp_unset_lock(&lock2); omp_unset_lock(&lock1); } #pragma omp section { omp_lock_t lock1, lock2; omp_set_lock(&lock2); omp_set_lock(&lock1); // Code omp_unset_lock(&lock1); omp_unset_lock(&lock2); } } } 1. T0 захватывает Lock1 1. T1 захватывает Lock2 2. T0 ожидает Lock2 2. T1 ожидает Lock1 62
  63. 63. OpenMP 4.0: Поддержка ускорителей (GPU) sum = 0; #pragma omp target device(acc0) in(B,C) #pragma omp parallel for reduction(+:sum) for (i = 0; i < N; i++) sum += B[i] * C[i]  omp_set_default_device()  omp_get_default_device()  omp_get_num_devices() 63
  64. 64. OpenMP 4.0: SIMD-конструкции  SIMD-конструкции для векторизации циклов (SSE, AVX2, AVX-512, AltiVec, …) void minex(float *a, float *b, float *c, float *d) { #pragma omp parallel for simd for (i = 0; i < N; i++) d[i] = min(distsq(a[i], b[i]), c[i]); } 64
  65. 65. OpenMP 4.0: Thread Affinity  Thread affinity – привязка потоков к процессорным ядрам  #pragma omp parallel proc_bind(master | close | spread)  omp_proc_bind_t omp_get_proc_bind(void)  Env. variable OMP_PLACES  export OMP_NUM_THREADS=16  export OMP_PLACES=0,8,1,9,2,10,3,11,4,12,5,13,6,14,7,15  export OMP_PROC_BIND=spread,close 65
  66. 66. OpenMP 4.0: user defined reductions #pragma omp declare reduction (reduction-identifier : typename-list : combiner) [identity(identity-expr)] #pragma omp declare reduction (merge : std::vector<int> : omp_out.insert(omp_out.end(), omp_in.begin(), omp_in.end() )) void schedule(std::vector<int> &v, std::vector<int> &filtered) { #pragma omp parallel for reduction (merge : filtered) for (std:vector<int>::iterator it = v.begin(); it < v.end(); it++) { if (filter(*it)) filtered.push_back(*it); } } 66
  67. 67. 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)) 67
  68. 68. 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 68
  69. 69. 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++ 69
  70. 70. Создание потоков 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 70
  71. 71. Завершение потоков  Возврат (return) из стартовой функции (start_routine)  Вызов pthread_exit()  Вызов pthread_cancel() другим потоком  Процесс (и его потоки) завершаются вызовом exit() 71
  72. 72. Создание и завершение потоков #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); } 72
  73. 73. Создание и завершение потоков #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); } 73
  74. 74. Ожидание потоков  Функция pthread_join() – позволяет дождаться завершения заданного потока  Поток может быть типа “detached” или “joinable” (default)  К detached-потоку не применима функция pthread_join (поток создается и существует независимо от других)  Joinable-поток требует хранения дополнительных данных  Тип потока можно задать через его атрибуты или вызвав функцию pthread_detach 74
  75. 75. Ожидание потоков #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); } 75
  76. 76. Синхронизация потоков  Функция pthread_self() – возвращает идентификатор потока  Функция pthread_equal() – позволяет сравнить идентификаторы двух потоков 76
  77. 77. Взаимные исключения (mutex)  Mutex (mutual exclusion) – это объект синхронизации “взаимное исключение”  Мьютексы используются для создания критических секций (critical sections) – областей кода, которые выполняются в любой момент времени только одним потоком  В критических секциях, как правило, содержится код работы с разделяемыми переменными  pthread_mutex_init() – инициализирует мьютекс  pthread_mutex_destroy() – уничтожает мьютекс  pthread_mutex_lock() – блокирует выполнение потока, пока он не захватит (acquire) мьютекс  pthread_mutex_trylock() – осуществляет попытку захватить мьютекс  pthread_mutex_unlock() – освобождает (release) мьютекс 77
  78. 78. Взаимные исключения (mutex) node_t *llist_delete(int value) { node_t *prev, *current; prev = &head; pthread_mutex_lock(&prev->lock); while ((current = prev->link) != NULL) { pthread_mutex_lock(&current->lock); if (current->value == value) { prev->link = current->link; pthread_mutex_unlock(&current->lock); pthread_mutex_unlock(&prev->lock); current->link = NULL; return current; } pthread_mutex_unlock(&prev->lock); prev = current; } pthread_mutex_unlock(&prev->lock); return NULL; } 78
  79. 79. Вычисление числа π  Приближенное вычисление числа π 1 𝜋= 0 4 2 𝑑𝑥 1+ 𝑥 𝑛 𝜋≈ℎ 𝑖=1 4 1 + (ℎ(𝑖 − 0.5))2 1 ℎ= 𝑛 4,50 4,00 3,50 3,00 2,50 2,00 1,50 1,00 0,50 0,00 0,00 0,10 0,20 0,30 0,40 0,50 0,60 0,70 0,80 0,90 79
  80. 80. 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; } 80
  81. 81. 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; } 81
  82. 82. Windows API для многопоточной обработки  Win32 API Threads CreateThread – системный вызов HANDLE WINAPI CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId );  C run-time library _beginthread (обертка вокруг CreateThread, корректно инициализирует libc) uintptr_t _beginthread(void *start_address(void *), unsigned int stack_size, void *arglist);  MFC AfxBeginThread – MFC-обертка вокруг CreateThread CWinThread *AfxBeginThread(...); 82
  83. 83. Windows API для многопоточной обработки  .NET System.Threading public sealed class Thread : CriticalFinalizerObject, _Thread 83
  84. 84. C11 threads #include <threads.h> void threadfun() { printf("Hello from threadn"); } int main() { thrd_t tid; int rc; rc = thrd_create(&tid, threadfun, NULL) if (rc != thrd_success) { fprintf(stderr, "Error creating threadn"); exit(1); } thrd_join(tid, NULL); return 0; } 84
  85. 85. Ссылки  Эхтер Ш., Робертс Дж. Многоядерное программирование. – СПб.: Питер, 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 85
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×