Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Параллельное программирование над общей памятью. POSIX Threads. Учебное пособие

.pdf
Скачиваний:
0
Добавлен:
12.08.2026
Размер:
1 Мб
Скачать

Нас интересуют второй и третий фреймы как относящиеся к нашему коду. Переключимся на второй фрейм:

(gdb) frame 2

#2

0x00000000004007cc in thread_func () at error.cpp:8

8

pthread_mutex_lock(&m);

Видно, что исполнение потока остановилось на ожидании мьютекса (строка 8). Просмотрим участок кода, где это произошло:

(gdb) list 3

4int a = 0;

5pthread_mutex_t m;

7void* thread_func(void*) {

8pthread_mutex_lock(&m);

9a++;

10

//

11return 0;

12}

После визуального анализа программного кода становится очевидно, что после доступа к разделяемому ресурсу (строка 9) пропущена команда освобождения мьютекса. Для решения проблемы необходимо добавить после строки 9 команду pthread_mutex_unlock(&m). Повторная компиляция и запуск программы позволят убедиться, что зависание исчезло и программа работает корректно.

Способ 2. Использование точек останова

Попробуем альтернативный подход к обнаружению проблемы. Откроем программу в отладчике

$ gdb ./a.out

и установим точку останова на строке 8, чтобы убедиться, что до момента обращения к разделяемому ресурсу программа работает корректно:

(gdb) break 8

Breakpoint 1 at 0x4007c2: file error.cpp, line 8.

61

Можно посмотреть информацию по точкам останова:

(gdb) info breakpoints

 

Num

Type

Disp Enb Address

What

1

breakpoint

keep y 0x00000000004007c2

in thread_func(void*)

 

 

 

at error.cpp:8

Запустим программу:

(gdb) r

Starting program: /home/gdb/a.out

[Thread debugging using libthread_db enabled]

Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". [New Thread 0x7ffff77ef700 (LWP 7908)]

[New Thread 0x7ffff6fee700 (LWP 7909)] [Switching to Thread 0x7ffff77ef700 (LWP 7908)]

Thread 2 "a.out" hit Breakpoint 1, thread_func () at error.cpp:8 8 pthread_mutex_lock(&m);

Видно, что породилось два дочерних потока. Посмотрим информацию о них более подробно:

(gdb) info threads

 

Id Target Id

Frame

1Thread 0x7ffff7fcc700 (LWP 7904) "a.out" clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:81

*2 Thread 0x7ffff77ef700 (LWP 7908) "a.out" thread_func () at error.cpp:8

3Thread 0x7ffff6fee700 (LWP 7909) "a.out" thread_func () at error.cpp:8

Добавим точку останова перед выходом из функции потока

(gdb) break 11

Breakpoint 2 at 0x4007db: file error.cpp, line 11.

и выведем еще раз список точек останова:

(gdb) info breakpoints

 

Num

Type

Disp Enb Address

What

1

breakpoint

keep y 0x00000000004007c2

in thread_func(void*)

 

 

 

at error.cpp:8

62

 

breakpoint

already hit 1 time

2

breakpoint

keep y 0x00000000004007db in thread_func(void*)

 

 

at error.cpp:11

Продолжаем исполнение:

(gdb) c Continuing.

[Switching to Thread 0x7ffff6fee700 (LWP 7909)]

Thread 3 "a.out" hit Breakpoint 1, thread_func () at error.cpp:8 8 pthread_mutex_lock(&m);

Поток номер 3 достиг первой точки останова на строке 8. Продолжаем исполнение:

(gdb) c Continuing.

[Switching to Thread 0x7ffff77ef700 (LWP 7908)]

Thread 2 "a.out" hit Breakpoint 2, thread_func () at error.cpp:11 11 return 0;

Поток номер 2 достиг второй точки останова. Посмотрим состояние потока 3:

(gdb) thread 3

[Switching to thread 3 (Thread 0x7ffff6fee700 (LWP 7909))]

#0

__lll_lock_wait ()

 

 

at ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135

135

2:

movl

%edx, %eax

Посмотрим стек вызовов:

(gdb) bt

#0 __lll_lock_wait ()

at ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135

#1 0x00007fca656c0dbd in __GI___pthread_mutex_lock (mutex=0x6010a0 <m>) at ../nptl/pthread_mutex_lock.c:80

#2 0x00000000004007cc in thread_func () at error.cpp:8

#3 0x00007fca656be6ba in start_thread (arg=0x7fca64aeb700)

63

at pthread_create.c:333

#4 0x00007fca653f43dd in clone ()

at ../sysdeps/unix/sysv/linux/x86_64/clone.S:109

Переключимся на второй фрейм:

(gdb) frame 2

#2

0x00000000004007cc in thread_func () at error.cpp:8

8

pthread_mutex_lock(&m);

Поток 3 находится на ожидании мьютекса. Проверим мьютекс:

(gdb) p pthread_mutex_trylock(&m) $1 = 16

GDB вычисляет указанное выражение (в данном случае – вызывает функцию pthread_mutex_trylock()) и возвращает результат (в данном случае – код ошибки). Найдем для этого кода строковое описание:

(gdb) p strerror(16)

$2 = 0x7ffff797d05e "Device or resource busy"

Код ошибки, возвращенный pthread_mutex_trylock(), говорит, что мьютекс занят. В то же время поток 2 уже завершил свое исполнение, т. е. освободить мьютекс некому. Следовательно, поток 3 будет ждать его вечно.

Проверим эту догадку, для чего создадим точку наблюдения

(gdb) watch pthread_mutex_trylock(&m) Watchpoint 3: pthread_mutex_trylock(&m)

и запустим программу на дальнейшее исполнение:

(gdb) c Continuing.

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

64

Попробуем добавить в функцию thread_func() из примера 8 вызов функции pthread_mutex_unlock(&m) после доступа к разделяемому ресурсу, так что функция thread_func() примет вид

void* thread_func(void*) { pthread_mutex_lock(&m); a++; pthread_mutex_unlock(&m);

//

return 0;

}

После этого пройдем сценарий отладки еще раз:

$ gdb ./a.out (gdb) break 8

Breakpoint 1 at 0x400812: file error.cpp, line 8. (gdb) break 10

Breakpoint 2 at 0x40082b: file error.cpp, line 10. (gdb) r

Starting program: /home/gdb/a.out

[Thread debugging using libthread_db enabled]

Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". [New Thread 0x7ffff77ef700 (LWP 8345)]

[New Thread 0x7ffff6fee700 (LWP 8346)] [Switching to Thread 0x7ffff77ef700 (LWP 8345)]

Thread 2 "a.out" hit Breakpoint 1, thread_func () at error.cpp:8 8 pthread_mutex_lock(&m);

(gdb) c Continuing.

[Switching to Thread 0x7ffff6fee700 (LWP 8346)]

Thread 3 "a.out" hit Breakpoint 1, thread_func () at error.cpp:8 8 pthread_mutex_lock(&m);

(gdb) c Continuing.

[Switching to Thread 0x7ffff77ef700 (LWP 8345)]

Thread 2 "a.out" hit Breakpoint 2, thread_func () at error.cpp:10 10 pthread_mutex_unlock(&m);

65

(gdb) c Continuing.

[Thread 0x7ffff77ef700 (LWP 8345) exited] [Switching to Thread 0x7ffff6fee700 (LWP 8346)]

Thread 3 "a.out" hit Breakpoint 2, thread_func () at error.cpp:10 10 pthread_mutex_unlock(&m);

(gdb) c Continuing.

[Thread 0x7ffff6fee700 (LWP 8346) exited] [Inferior 1 (process 8341) exited normally]

Ошибка устранена, и программа успешно завершается.

ДОПОЛНИТЕЛЬНАЯ ЛИТЕРАТУРА

1.Кулямин В. В. Методы верификации программного обеспечения [Электронный ресурс] // Портал «Информационно-коммуникационные технологии

вобразовании». – URL: http://www.ict.edu.ru/ft/005645/62322e1-st09.pdf (дата обращения: 08.11.2017).

2.Rahul P., George B. Concurrency: Tools And Techniques to Identify Concurrency Issues [Electronic resource] // MSDN Magazine. – URL: http://down-

load.microsoft.com/download/3/a/7/3a7fa450-1f33-41f7-9e6d-3aa95b5a6aea/ MSDNMagazine2008_06en-us.chm (accessed: 08.11.2017).

3. Кудрин М.Ю., Прокопенко А.С., Тормасов А.Г. Метод нахождения со-

стояний гонки в потоках, работающих на разделяемой памяти // Труды МФТИ. – URL: https://mipt.ru/upload/e08/f_edv8-arphcxl1tgs.pdf (дата обращения: 08.11.2017).

4. Колосов А.П. Методы отладки параллельных программ // Известия Тульского государственного университета. – URL: https://tidings.tsu.tula.ru/ tidings/pdf/web/preview_therest_ru.php?x=tsu_izv_technical_sciences_2010_02_ part_2&year=2010 (дата обращения: 08.11.2017).

5. Krishna K., Alireza M., Deng-jyi Chen. Modeling multithreaded applications using Petri nets [Electronic resource] // International Journal of Parallel Programming. – URL: http://csrl.cse.unt.edu/kavi/Research/IJPP.pdf (accessed: 08.11.2017).

6. GDB: The GNU Project Debugger [Electronic resource] // Операционная система GNU. – URL: https://www.gnu.org/software/gdb/documentation/ (accessed: 08.11.2017).

66

5. ЛАБОРАТОРНЫЙ ПРАКТИКУМ

КОМАНДЫ ОПЕРАЦИОННОЙ СИСТЕМЫ

Лабораторные работы выполняются в операционной системе Linux. Ниже рассматриваются некоторые команды Linux, которые помогут облегчить разработку параллельных программ. Для каждой команды приводятся лишь сведения, полезные для выполнения практикума. За дополнительной информацией о работе команд и возможных опциях следует обратиться к книге [5] и официальной документации, ссылки на которую приведены в дополнительной литературе к настоящему разделу.

Команда less. Позволяет вывести на экран содержимое указанного файла и передвигаться по тексту с помощью клавиш управления

курсором (« », « », «PgUp», «PgDown»). Для выхода из режима просмотра необходимо нажать клавишу «Q». Например, команда

$ less /proc/cpuinfo

выведет на экран содержимое файла /proc/cpuinfo.

Команда grep. Позволяет вывести только те строки файла, которые содержат заданный шаблон, описываемый с помощью регулярных выражений. Например, команда

$ grep processor /proc/cpuinfo

выведет только те строки файла /proc/cpuinfo, которые содержат подстроку processor.

Команда ps. Позволяет отобразить список процессов операционной системы (в момент вызова команды). Например, команда

$ ps

67

выводит информацию о процессах, ассоциированных с текущим терминалом; команда

$ ps ax

выводит информацию о всех работающих в настоящее время процессах; команда

$ ps u

позволяет узнать, какой процент процессорного времени (столбец %CPU) и памяти (столбец %MEM) занимают запущенные текущим пользователем процессы, а также общее время работы этих процессов (столбец TIME); команда

$ ps -mfL

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

LWP).

Командой ps удобно пользоваться совместно с командой less для пролистывания выводимой информации с помощью кнопок вверх/вниз:

$ ps ax | less

Команда top. Позволяет отображать список процессов операционной системы и их активность «в реальном времени». На рис. 3 изображено окно терминала, в котором запущена команда top. В верхней части экрана отображается общая информация: количество задач и их статус, текущая загрузка процессора и памяти, файла подкачки и другая полезная информация. Ниже расположена таблица, в которой перечислены процессы (отображается часть процессов, помещающаяся в окно, с учетом текущих настроек сортировки).

Выводом команды top можно управлять с помощью нажатия клавиш:

1 – включает SMP-режим: отображает загрузку каждого процессора (ядра); повторное нажатие отключает режим;

A – сортировка процессов по возрасту;

P – сортировка процессов по использованию процессора;

M – сортировка процессов по использованию памяти;

T – сортировка процессов по времени выполнения.

68

Рис. 3. Вывод команды top

Команда kill. Позволяет послать заданному процессу любой сигнал. Например, команда

$ kill -9 12345

позволяет принудительно завершить процесс с номером 12345, послав ему сигнал SIGKILL. Такое действие может потребоваться, если процесс завис и не может завершиться самостоятельно.

Компиляция программ

Существует несколько реализаций Pthreads под различные операционные системы. В рамках практикума будет использоваться библиотека libpthread для ОС Linux, а в качестве компилятора – GNU GCC.

Для использования API, предоставляемого Pthreads, необходимо подключить заголовочный файл pthread.h:

#include <pthread.h>

Для корректной компиляции многопоточных программ на базе Pthreads необходимо использовать опцию компилятора -pthread, ко-

69

торая для GCC эквивалентна заданию двух опций (при компиляции под Linux):

-lpthread – указывает компоновщику на необходимость подключения библиотеки libphread для поддержки потоков POSIX (в зависимости от того, какой режим сборки задан другими ключами, подключится либо статическая библиотека libpthread.a, либо динамическая библиотека libpthread.so);

-D_REENTRANT – указывает компилятору на необходимость определения макроса _REENTRANT, что приведет к использованию по-

токобезопасных объявлений стандартной библиотеки C.

Таким образом, если имеется программа на языке С++ с именем threads_demo.cpp, ее можно скомпилировать командой

$ g++ threads_demo.cpp -pthread -o threads_demo

Для использования отладчика программу нужно скомпилировать с отладочной информацией (опция компилятора -g). Желательно также отключить оптимизацию (опция компилятора -O0) для предотвращения удаления / слияния переменных, раскрутки циклов и прочих оптимизаций. Например, для отладки многопоточной программы с именем threads_demo.cpp можно использовать команду:

$ g++ threads_demo.cpp -pthread -g -O0 -o threads_demo

ИССЛЕДОВАНИЕ ПРОИЗВОДИТЕЛЬНОСТИ ПАРАЛЛЕЛЬНЫХ ПРОГРАММ

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

Время исполнения программы

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

70

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]