Перейти к тексту

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

При первом входе в developerWorks для Вас будет создан профиль. Выберите информацию отображаемую в Вашем профиле — скрыть или отобразить поля можно в любой момент.

Вся введенная информация защищена.

  • Закрыть [x]

При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

Вся введенная информация защищена.

  • Закрыть [x]

Предотвращение утечек памяти в многопоточном программировании с использованием POSIX Thread

Советы по обнаружению и предотвращению утечек памяти при работе с потоками POSIX

Вэй Дун Си, разработчик решений IBM Systems Director, IBM
Author photo of Wei Dong Xie
Последние три года Вэй Дун работает разработчиком решений IBM Systems Director. В круг его обязанностей входит решение проблем, возникающих у заказчиков. Прежде чем начать работать в IBM, он прошел 10-месячную интернатуру в Intel в качестве Linux-разработчика. В 2007 году Вэй Дун получил степень магистра Университета г. Нанкин, Китай.

Описание:  Многопоточное программирование с использованием стандарта POSIX Thread (многопоточных библиотек pthread) определяет стандартный набор типов, функций и констант в языке программирования C, а сами библиотеки pthreads являются мощным инструментом управления потоками. При работе с потоками следует избегать распространенных ошибок. Например, если забыть присоединить присоединяемые потоки, могут возникнуть утечки памяти и, как следствие, бесполезное расходование системных ресурсов. В этой статье вы познакомитесь с основами потоков POSIX, а также научитесь обнаруживать и предотвращать утечки памяти.

Дата:  21.06.2011
Уровень сложности:  простой PDF:  A4 and Letter (29 КБ | 7 страница)Загрузить Adobe® Reader®
Активность:  3378 просмотров
Комментарии:  


Введение в потоки POSIX

Основной целью использования потоков является повышение быстродействия приложений. При таком подходе для работы приложений требуется меньше системных ресурсов операционной системы. Все потоки внутри одного процесса используют общее адресное пространство; благодаря чему реализация взаимодействий между потоками является более простой и эффективной по сравнению с реализацией взаимодействий между процессами. Например, пока один поток ожидает завершения вызова ввода/вывода, остальные потоки могут выполнять ресурсоемкие задачи. С помощью потоков можно задавать приоритеты задач: более важные задачи могут выполняться с повышенным приоритетом или даже прерывать выполнение менее приоритетных задач. С помощью гибкого планирования выполнение редких, единичных заданий можно размещать в интервалах между выполнением периодических заданий. И наконец, библиотеки pthreads идеально подходят для задач параллельного программирования на многопроцессорных компьютерах.

Однако главная причина использования потоков POSIX еще более проще: являясь частью стандартизированного интерфейса программирования потоков языка C, они отличаются высокой переносимостью.

Многопоточное программирование с использованием POSIX Thread имеет множество преимуществ, однако чтобы избежать утечек памяти и написать хорошо поддающийся отладке код, необходимо следовать некоторым основным правилам. Начнем с рассмотрения потоков POSIX, которые могут быть либо присоединяемыми (joinable), либо обособленными (detached).

Присоединяемые (joinable) потоки

Если при создании нового потока требуется знать, как он будет впоследствии уничтожен, необходимо использовать присоединяемый поток. Для присоединяемых потоков в системе выделяется приватная область, в которой сохраняется статус уничтожения потока. После уничтожения потока этот статус обновляется; получить его можно с помощью функции pthread_join(pthread_t thread, void** value_ptr).

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

Обособленные (detached) потоки

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

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


Демонстрация утечки памяти

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

Например, в операционной системе Red Hat Enterprise Linux (RHEL4) для каждого потока требуется выделить в памяти стек размером в 10 МБ. Это означает, что если поток не присоединяется, возникает утечка как минимум 10 МБ памяти. Предположим, вы создаете приложение для обработки входящих запросов пользователей, в котором для каждого пользователя создается поток, выполняющий определенные задачи и затем уничтожающийся. Если эти потоки являются присоединяемыми, и вы не вызываете функцию pthread_join() для их присоединения, каждый такой поток при уничтожении будет приводить к утечке значительного объема памяти (как минимум 10 МБ на каждый стек). В итоге постоянно возрастающий размер утечек приведет к тому, что новые потоки невозможно будет создавать по причине отсутствия свободной памяти.

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


Листинг 1. Код, приводящий к утечке памяти
#include<stdio.h>
#include<pthread.h>
void run() {
   pthread_exit(0);
}

int main () {
   pthread_t thread;
   int rc;
   long count = 0;
   while(1) {
      if(rc = pthread_create(&thread, 0, run, 0) ) {
         printf("ERROR, rc is %d, so far %ld threads created\n", rc, count);
         perror("Fail:");
         return -1;
      }
      count++;
   }
   return 0;
}

С помощью вызова функции pthread_create() в листинге 1 создается новый присоединяемый поток с атрибутами по умолчанию. Непрерывные обращения к этой функции выполняются вплоть до возникновения ошибки, после чего программа выводит ее код и причину.

При компиляции этого кода в ОС Red Hat Enterprise Linux Server 5.4 с помощью команды [root@server ~]# cc -lpthread thread.c -o thread вы получите результат, показанный в листинге 2.


Листинг 2. Результаты утечки памяти
[root@server ~]# ./thread
ERROR, rc is 12, so far 304 threads created
Fail:: Cannot allocate memory

После создания 304 потоков возникает ошибка с кодом 12, означающим отсутствие свободной памяти, и дополнительные потоки создаваться уже не могут.

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

В операционной системе Red Hat Enterprise Linux для каждого потока POSIX выделяется стек размером в 10 МБ. Другими словами, для каждой функции pthread выделяется приватная область памяти размером по меньшей мере в 10 МБ. В нашем примере, прежде чем процесс завершился с ошибкой, было создано 304 потока, которые заняли в памяти 304*10 МБ (т. е. приблизительно 3 ГБ). Общий объем доступной виртуальной памяти составляет 4 ГБ, причем четвертая его часть зарезервирована под ядро Linux. Путем несложных вычислений можно определить, что для выполнения пользовательских процессов доступно 3 ГБ оперативной памяти. Таким образом, мертвыми потоками было израсходовано 3 ГБ памяти, что является серьезной утечкой памяти. Также легко видеть, насколько быстро это произошло.

Чтобы предотвратить утечки памяти, необходимо добавить код для вызова функции pthread_join(), которая присоединяет все присоединяемые потоки.


Обнаружение утечек памяти

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

  1. Подсчитайте количество стеков потоков процесса. В их число входят все выполняющиеся активные потоки, а также уничтоженные потоки.
  2. Подсчитайте количество выполняющихся активных потоков процесса.
  3. Сравните эти два значения. Если количество существующих стеков потоков больше, чем количество выполняющихся активных потоков, и разница между этими значениями продолжает увеличиваться по мере работы программы, значит, имеет место утечка памяти.

Вероятнее всего, такая утечка памяти является следствием не присоединения присоединяемых потоков.

Использование утилиты pmap для подсчета стеков потоков

В запущенном процессе количество стеков потоков равно количеству тел потоков. Тела потоков включают выполняющиеся активные потоки и мертвые присоединяемые потоки.

Утилита операционной системы Linux pmap показывает статистику использования памяти процессами. Чтобы определить количество стеков потоков, выполните следующую команду.

[root@server ~]# pmap PID | grep 10240 | wc -l

(10240 КБ – размер стека по умолчанию в Red Hat Enterprise Linux Server 5.4.)

Использование каталога /proc/PID/task для подсчета активных потоков

Каждый раз при создании и выполнении потока в директорию /proc/PID/task добавляется запись. Когда поток (неважно, присоединяемый или обособленный) уничтожается, запись из этой директории удаляется. Таким образом, количество выполняющихся активных потоков можно подсчитать с помощью следующей команды.

[root@server ~]# ls /proc/PID/task | wc -l.

Сравнение полученных значений

Сравните значения, полученные в результате выполнения команд pmap PID | grep 10240 | wc -l и ls /proc/PID/task | wc -l. Если количество стеков потоков больше, чем количество активных потоков, и разница между этими значениями продолжает увеличиваться по мере работы программы, значит, имеет место утечка памяти.


Предотвращение утечек памяти

Все присоединяемые потоки должны быть присоединены в коде приложения. При создании присоединяемых потоков не забывайте вызывать функцию pthread_join(pthread_t, void**) для очистки области памяти, выделенной потоку, в противном случае возникнут серьезные утечки памяти.

На этапе тестирования приложения утечку памяти можно обнаружить с помощью команды pmap и анализа содержимого каталога /proc/PID/task. Если утечка существует, проверьте исходный код и убедитесь, что все присоединяемые потоки были присоединены.

Вот и все. Несколько простых действий по предотвращению утечек памяти упростят вам последующую работу и избавят от сопутствующих проблем.


Ресурсы

Научиться

Обсудить

Об авторе

Author photo of Wei Dong Xie

Последние три года Вэй Дун работает разработчиком решений IBM Systems Director. В круг его обязанностей входит решение проблем, возникающих у заказчиков. Прежде чем начать работать в IBM, он прошел 10-месячную интернатуру в Intel в качестве Linux-разработчика. В 2007 году Вэй Дун получил степень магистра Университета г. Нанкин, Китай.

Помощь по сообщениям о нарушениях

Сообщение о нарушениях

Спасибо. Эта запись была помечена для модератора.


Помощь по сообщениям о нарушениях

Сообщение о нарушениях

Сообщение о нарушении не было отправлено. Попробуйте, пожалуйста, позже.


developerWorks: вход


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


При первом входе в developerWorks для Вас будет создан профиль. Выберите информацию отображаемую в Вашем профиле — скрыть или отобразить поля можно в любой момент.

Выберите ваше отображаемое имя

При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

(Должно содержать от 3 до 31 символа.)


Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Оценить эту статью

Комментарии

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux
ArticleID=681842
ArticleTitle=Предотвращение утечек памяти в многопоточном программировании с использованием POSIX Thread
publish-date=06212011
author1-email=xieweid@cn.ibm.com
author1-email-cc=

Теги

Help
Используйте форму поиска, чтобы найти любой контент с данным тегом в My developerWorks. Используйте ползунок, чтобы отразить больше или меньше тегов.

КнопкаПопулярные теги отображает самые распространенные теги для данной области контента (например: Java, Linux, WebSphere).

Кнопка Мои теги отображает Ваши теги для данной области контента (например: Java, Linux, WebSphere).

Используйте форму поиска, чтобы найти любой контент с данным тегом в My developerWorks. Кнопка Популярные теги отображает самые распространенные теги для данной области контента (например: Java, Linux, WebSphere). Кнопка Мои теги отображает Ваши теги для данной области контента (например: Java, Linux, WebSphere).