пятница, 9 апреля 2010 г.

Несостоятельность ithreads в Perl (сам не ожидал)

Предыстория
Никогда до этого не использовал треды(threads)  в Perl - как-то не доверял я им и использовал forkи... и как оказалось не зря!
И настал тот день, когда от тредов никуда не убежишь(на самом деле убежишь, но про это дальше) - нужно было включить многопоточность для модуля Fuse.pm(просто передается дополнительный параметр threaded => 1), и тогда на каждый вызов файлофай системы будет создаваться тред.

Собственно сабж:
  1. Переменные по-умолчанию не являются общими между тредами. Это означает, что каждый раз когда вы стартуете тред, все структуры данных копируются в новый тред. Все - это значит все, включая внутренности всех пакетов, глобальные переменные, лексические...
  2. Общие переменные на самом деле - не общие, а просто связанные(Tied), со всеми вытекающими последствиями - требуют больше памяти и более медленные. Подробней тут http://www.perlmonks.org/index.pl?node_id=288022
  3. Самое печальное. Треды ЛИКАЮТ!!! Каждый запущенный тред после своего завершения не возвращает часть памяти!!!
  4. Треды в Perl требуют больше памяти чем fork-и !!! Да именно так :), поскольку fork-и используют COPY-ON-WRITE при копировании процесса в памяти.
  5. Нужен Perl скомпилированный с поддержкой тредов.
Вот простой тест на лики памяти: 

Достаточно запустить и понаблюдать за потреблением памяти процессом.
use threads;
while (1) {
    threads->create(sub{})->join();
}
Решение.
Использовать fork-и :) (естественно там, где они нативно поддерживаются). По существу:
  1. Fork-и не ликают
  2. Fork-и понятней и не подвержены ошибкам связанными с семафорами и локами данных. 
  3. Сейчас в большинстве unix системах fork-и - дешевые(в Linux в том числе), поскольку используется COPY-ON-WRITE метод. То есть, при создании нового процесса содержимое памяти не копируется. Накладные расходы только на организацию нового процесса.
  4. Работают на любой версии Perl
А что если выхода нет? Как в случае c модулем Fuse.pm? Я уже думал, что придется отказаться от многопоточности, но наткнулся на модуль forks. Этот модуль являет собой прагму, которая заменяет нативную реализацию тредов на использование fork-ов. 
Представьте себе, что есть код написанный с использованием тредов, просто напишите вначале кода use forks; и код будет использовать fork-и вместо тредов ( с Fuse.pm работает ).
Вот пример( и код перестает ликать):
use forks;
use threads;

while (1) {
    threads->create(sub{})->join();
}


8 коммент.:

Анонимный комментирует...

что за слово ЛИКАТЬ...
Пиши либо по английски: "memory leak", либо по-русски: "утечка памяти"

koorchik комментирует...

привычка... сленг... надеюсь, что в этот раз все поймут :)

Полностью согласен, что лучше писать "утечка памяти" либо "memory leak"

ichesnokov комментирует...

Спасибо за подсказку по поводу forks. Однажды пытался устранить утечку памяти в скрипте, использующем threads, пытался отловить её различными утилитами типа Devel::Leak, Devel::LeakTrace, но ничего не получилось и я забросил это дело.
То, что "текут" сами треды - не было даже мысли.

Unknown комментирует...

К первому комментарию.
А мне нравится слово ЛИКАТЬ!!! Я буду его использовать на ровне с глаголами ГУГЛИТЬ, ПРОГАТЬ и ХАКНУТЬ.
Нашелся тут учитель русского языка и литературы. Слово "ЛИКАТЬ" ему не подуше :)
Как ты себе это представляешь:
"Треды утечка памяти"? Или "Треды утекают памятью"? Или "Треды memory leak"? Фигня какая-то.

А по теме. Молодец, Курец. Давай по больше таких постов. Хотелось бы услышать в каких случаях стоит использовать треды (не зря же их придумали). :)

koorchik комментирует...

Может треды придумали для эмуляции fork-ов под виндой( "The initial impetus for implementing ithreads was to emulate fork for Microsoft systems" - цитата с Camel book ) ;)... ну и на маленьких по размеру процессах(до 15 мб) треды были бы неплохи если бы не ликали.

Небольшой тест:
Файл test.pl
use threads;
my $test = 'x' x (15*1024**2);
for (1..1_000) {
threads->create(sub{})->join();
}

time perl test.pl
real 0m12.139s
user 0m12.029s
sys 0m0.101s

time perl -Mforks test.pl
real 0m10.450s
user 0m6.157s
sys 0m5.666s

Есть еще разница при работе с общей памятью процесса и прогоном данных через пайп... но нужно смотреть конкретные примеры.
Естественно в fork-ах можно также использовать общий пул данных. Есть много модулей для этого, например - IPC::ShareLite ...

Анонимный комментирует...

Главное что решение простое и легкое, просто новый use добавить, красиво

2 Антон
У Трэдов наблюдается утечка памяти :)

Анонимный комментирует...

Спасибо за наводку на модуль fork!
http://laziness-impatience-hubris.blogspot.com/2009/12/threads.html

Анонимный комментирует...

К сожелению в данном модуле нехватает функционала. Например неработают модули:
http://search.cpan.org/~tag/POE-Component-Pool-DBI-0.014/
http://search.cpan.org/~tag/POE-Component-Pool-Thread-0.015/
Хотя POE-Component-Pool-Thread-0.015 это совсем другая парадигма ...

Отправить комментарий

Не забудьте добавить себя в постоянные читатели и включить уведомления о новых комментариях, либо воспользуйтесь RSS каналом ;)