LXF108:Дырки в паутине
Yaleks (обсуждение | вклад) (Новая: == Дырки в паутине == : ''Утечка ресурсов – в первую очередь, памяти – одна из проблем современных сложны...) |
Акроним (обсуждение | вклад) м (Акроним переименовал страницу LХF100-101:Зaпycк пo ceти в LXF108:Дырки в паутине поверх перенаправления) |
||
(не показаны 5 промежуточных версий 2 участников) | |||
Строка 1: | Строка 1: | ||
== Дырки в паутине == | == Дырки в паутине == | ||
− | : ''Утечка ресурсов | + | : ''Утечка ресурсов — в первую очередь, памяти — одна из проблем современных сложных приложений. '''Андрей Кузьменко''' покажет, как избежать ее в ваших программах.'' |
Можно смело считать 2007 год переломным моментом | Можно смело считать 2007 год переломным моментом | ||
Строка 9: | Строка 9: | ||
текущем году производители чипов уже делают упор на четырехъядерные процессоры. Очевидно, что производители ПО должны адекватно реагировать на эти изменения, выпуская продукты, задействующие все преимущества новых технологий. А на чем они пишутся? | текущем году производители чипов уже делают упор на четырехъядерные процессоры. Очевидно, что производители ПО должны адекватно реагировать на эти изменения, выпуская продукты, задействующие все преимущества новых технологий. А на чем они пишутся? | ||
+ | {{Врезка | ||
+ | |Заголовок=Мы про это уже писали | ||
+ | |Содержание=Если вы хотите | ||
+ | узнать больше | ||
+ | о программировании с использованием Pthreads, | ||
+ | обратитесь к | ||
+ | нашим учебникам, | ||
+ | опубликованным | ||
+ | в [[LXF86:Unix API|LXF86]]-[[LXF87-88:Unix API|87/88]]. | ||
+ | |Ширина=100px}} | ||
В языке C++, в отличие, например, от Java, отсутствует встроенная поддержка многопоточного программирования. Иными словами, с помощью «голого» C++ нельзя создавать соответствующие | В языке C++, в отличие, например, от Java, отсутствует встроенная поддержка многопоточного программирования. Иными словами, с помощью «голого» C++ нельзя создавать соответствующие | ||
стандарту языка многопоточные приложения. Разумеется, данная | стандарту языка многопоточные приложения. Разумеется, данная | ||
Строка 24: | Строка 34: | ||
=== И снова классы… === | === И снова классы… === | ||
Одной из серьезных проблем языка C++ (настолько серьезной, | Одной из серьезных проблем языка C++ (настолько серьезной, | ||
− | что иные компании создают целые платформы со сборкой | + | что иные компании создают целые платформы со сборкой мусора, лишь бы с нею не сталкиваться) является утечка ресурсов. Это |
− | + | может быть память, выделенная с помощью оператора new, файловые дескрипторы, сетевые сокеты, мьютексы. Для освобождения | |
− | может быть память, выделенная с помощью оператора new, | + | ресурсов, используемых объектом, в C++ предусмотрены специальные методы — деструкторы. Задача программиста — правильно |
− | + | ||
− | ресурсов, используемых объектом, в C++ предусмотрены | + | |
− | + | ||
написать деструктор и убедиться в том, что в программе происходит | написать деструктор и убедиться в том, что в программе происходит | ||
− | его вызов. Только так можно гарантировать, что ресурсы будут | + | его вызов. Только так можно гарантировать, что ресурсы будут возвращены системе. |
− | + | ||
Во всех тестах, описываемых в этой статье, будет использоваться класс checker, объявленный в файле checker.hpp. Вот он: | Во всех тестах, описываемых в этой статье, будет использоваться класс checker, объявленный в файле checker.hpp. Вот он: | ||
Строка 58: | Строка 64: | ||
В документе The Open Group Base Specifications Issue 6 IEEE Std | В документе The Open Group Base Specifications Issue 6 IEEE Std | ||
1003.1, 2004 Edition в разделе, посвященном функции pthread_exit(), | 1003.1, 2004 Edition в разделе, посвященном функции pthread_exit(), | ||
− | по этому поводу говорится следующее [здесь и далее перевод | + | по этому поводу говорится следующее [здесь и далее перевод автора]: «Функция void pthread_exit(void *value_ptr) завершает вызывающий поток и делает значение value_ptr доступным для успешного присоединения к завершающему потоку. Любые обработчики |
− | + | ||
− | + | ||
− | + | ||
отмены, помещенные в стек, но еще не извлеченные из него, будут | отмены, помещенные в стек, но еще не извлеченные из него, будут | ||
− | извлечены в порядке, обратном по отношению к порядку | + | извлечены в порядке, обратном по отношению к порядку помещения в стек, а после выполнены. Если потоку принадлежат данные, то |
− | + | после выполнения всех обработчиков отмены будут вызваны соответствующие функции деструкторов, при этом порядок их вызова | |
− | после выполнения всех обработчиков отмены будут вызваны | + | |
− | + | ||
не определен. При завершении потока ресурсы процесса, включая | не определен. При завершении потока ресурсы процесса, включая | ||
− | мьютексы и дескрипторы файлов, не освобождаются, и не | + | мьютексы и дескрипторы файлов, не освобождаются, и не выполняются никакие восстановительные действия уровня процесса, включая всевозможные вызовы любых функций atexit()». То есть, если |
− | + | объект в потоковой функции представляет собой локальную переменную с классом памяти auto, то после вызова pthread_exit() для | |
− | + | ||
− | объект в потоковой функции представляет собой локальную | + | |
− | + | ||
него должен быть автоматически выполнен деструктор. Однако, как | него должен быть автоматически выполнен деструктор. Однако, как | ||
показывает практика, бывают случаи, когда этого не происходит. | показывает практика, бывают случаи, когда этого не происходит. | ||
Рассмотрим следующую программу: | Рассмотрим следующую программу: | ||
− | < | + | <source lang="cpp">void* task1(void *X) { |
− | std::cout<<" Start task_1!"<<std::endl; | + | std::cout<<" Start task_1!"<<std::endl; |
− | checker P("First"); P.calc(5); | + | checker P("First"); P.calc(5); |
− | pthread_exit(NULL); | + | pthread_exit(NULL); |
− | return 0; | + | return 0; |
} | } | ||
void* task2(void *X) { | void* task2(void *X) { | ||
− | std::cout<<" Start task_2!"<<std::endl; | + | std::cout<<" Start task_2!"<<std::endl; |
− | checker Q("Second"); Q.calc(8); | + | checker Q("Second"); Q.calc(8); |
− | return 0; | + | return 0; |
} | } | ||
int main(void){ | int main(void){ | ||
− | std::cout<<" Start test #1!"<<std::endl; | + | std::cout<<" Start test #1!"<<std::endl; |
− | pthread_t threadA, threadB; | + | pthread_t threadA, threadB; |
− | pthread_create(&threadA, NULL, task1, NULL); | + | pthread_create(&threadA, NULL, task1, NULL); |
− | pthread_detach(threadA); | + | pthread_detach(threadA); |
− | pthread_create(&threadB, NULL, task2, NULL); | + | pthread_create(&threadB, NULL, task2, NULL); |
− | pthread_detach(threadB); | + | pthread_detach(threadB); |
− | pthread_exit(NULL); | + | pthread_exit(NULL); |
− | return 0; | + | return 0; |
}</source> | }</source> | ||
Здесь объявлены две потоковых функции: task1() и task2(). В | Здесь объявлены две потоковых функции: task1() и task2(). В | ||
качестве элемента данных в каждой из них используется объект | качестве элемента данных в каждой из них используется объект | ||
класса checker, выделенный в стеке. Функция task1() завершается | класса checker, выделенный в стеке. Функция task1() завершается | ||
− | принудительно с помощью pthread_exit(), а task2() выходит | + | принудительно с помощью pthread_exit(), а task2() выходит «естественным образом» через return 0. При этом потоки создаются |
− | + | как открепленные, то есть при их уничтожении ресурсы, которые они | |
− | как открепленные, | + | |
использовали, сразу же возвращаются системе. | использовали, сразу же возвращаются системе. | ||
− | Рассмотрим результат работы программы в различных | + | Рассмотрим результат работы программы в различных дистрибутивах Linux. Например, в SLAX 6.0.3 вывод на консоль будет таким: |
− | + | ||
<pre>Start test #1! | <pre>Start test #1! | ||
Start task_1! | Start task_1! | ||
Строка 122: | Строка 118: | ||
Destructor done! Name:Second</pre> | Destructor done! Name:Second</pre> | ||
Видите? Деструктор объекта из потоковой функции task1() | Видите? Деструктор объекта из потоковой функции task1() | ||
− | вызван не был! Конечно, 3.0.4 | + | вызван не был! Конечно, 3.0.4 — не самая актуальная версия данного дистрибутива, но, как мы увидим далее, аналогичные проблемы |
− | + | ||
имеют место и в более современных ОС. | имеют место и в более современных ОС. | ||
− | === Поел | + | === Поел — убери за собой === |
− | Очень часто при завершении потока бывает необходимо | + | Очень часто при завершении потока бывает необходимо выполнить некоторые заключительные операции: освободить память, |
− | + | ||
закрыть файлы, снять блокировки с разделяемых переменных и | закрыть файлы, снять блокировки с разделяемых переменных и | ||
т.п. Желательно, чтобы эти действия выполнялись единообразно, | т.п. Желательно, чтобы эти действия выполнялись единообразно, | ||
− | как для стандартного завершения потоковой функции | + | как для стандартного завершения потоковой функции оператором return, так и при аннулировании другим потоком. Библиотека |
− | + | Pthreads предоставляет для этого возможность, называемую «стеком очистительно-восстановительных операций». Как она работает? С каждым потоком, имеющимся в программе, связывается | |
− | Pthreads предоставляет для этого возможность, называемую | + | стек очистительно-восстановительных операций, который содержит указатели на функции, вызываемые во время аннулирования |
− | + | ||
− | + | ||
− | стек очистительно-восстановительных операций, который | + | |
− | + | ||
(завершения) потока. Для работы с данным стеком используются | (завершения) потока. Для работы с данным стеком используются | ||
две функции (или макроса): | две функции (или макроса): | ||
− | pthread_cleanup_push() Принимает в качестве параметров | + | * pthread_cleanup_push() Принимает в качестве параметров указатель на помещаемую в стек функцию и передаваемый ей аргумент; |
− | + | * pthread_cleanup_pop() Принимает в качестве параметра целочисленное значение и извлекает завершающую функцию с вершины стека. Если аргумент отличен от нуля, завершающая функция выполняется. | |
− | pthread_cleanup_pop() Принимает в качестве параметра | + | |
− | + | ||
− | + | ||
− | выполняется. | + | |
Давайте рассмотрим еще один пример. Здесь мы определяем | Давайте рассмотрим еще один пример. Здесь мы определяем | ||
потоковую функцию, которая в зависимости от значения параметра, | потоковую функцию, которая в зависимости от значения параметра, | ||
заданного пользователем, завершается либо «обычным образом», | заданного пользователем, завершается либо «обычным образом», | ||
либо вызовом pthread_exit(): | либо вызовом pthread_exit(): | ||
− | void* task1(void *X){ | + | <source lang="cpp">void* task1(void *X){ |
− | std::cout<<" Start test thread!"<<std::endl; | + | std::cout<<" Start test thread!"<<std::endl; |
− | checker *Z = new checker("agent"); | + | checker *Z = new checker("agent"); |
− | pthread_cleanup_push(del_ptr_checker, Z); | + | pthread_cleanup_push(del_ptr_checker, Z); |
− | int *counter = static_cast<int*>(X); | + | int *counter = static_cast<int*>(X); |
− | for(int i=0; i<(*counter); ++i) | + | for(int i=0; i<(*counter); ++i) |
− | { | + | { |
− | if(i==1000) pthread_exit(NULL); | + | if(i==1000) pthread_exit(NULL); |
− | } | + | } |
− | pthread_cleanup_pop(1); | + | pthread_cleanup_pop(1); |
− | std::cout<<" Thread go boom!"<<std::endl; | + | std::cout<<" Thread go boom!"<<std::endl; |
− | return 0; | + | return 0; |
− | } | + | }</source> |
Обратите внимание, что в качестве элемента данных теперь | Обратите внимание, что в качестве элемента данных теперь | ||
− | используется экземпляр класса checker, расположенный в | + | используется экземпляр класса checker, расположенный в динамической памяти. Для ее освобождения была написана функция del_ptr_checker(), указатель на которую помещается в стек |
− | + | очистительно-восстановительных операций. Мы вызываем деструктор объекта при помощи pthread_cleanup_pop(1) — кажется, это | |
− | + | ||
− | очистительно-восстановительных операций. Мы вызываем | + | |
− | + | ||
должно гарантировать выполнение очистительных действий вне | должно гарантировать выполнение очистительных действий вне | ||
зависимости от способа завершения потока. | зависимости от способа завершения потока. | ||
Функция del_ptr_checker() сама по себе довольно проста: | Функция del_ptr_checker() сама по себе довольно проста: | ||
− | void del_ptr_checker(void *X){ | + | <source lang="cpp">void del_ptr_checker(void *X){ |
checker *del = static_cast<checker*>(X); | checker *del = static_cast<checker*>(X); | ||
std::cout<<" #-> START del_ptr_checker!"<<std::endl; | std::cout<<" #-> START del_ptr_checker!"<<std::endl; | ||
Строка 178: | Строка 162: | ||
del = 0; | del = 0; | ||
std::cout<<" #-> END del_ptr_checker!"<<std::endl; | std::cout<<" #-> END del_ptr_checker!"<<std::endl; | ||
− | } | + | }</source> |
− | Мы приводим переданный указатель к типу checker * и | + | Мы приводим переданный указатель к типу checker * и вызываем оператор delete для освобождения памяти. del_ptr_checker() |
− | + | ||
определена в файле helper.hpp. Функция main() для нашего примера | определена в файле helper.hpp. Функция main() для нашего примера | ||
выглядит так: | выглядит так: | ||
− | int main(void) | + | <source lang="cpp">int main(void) |
{ | { | ||
std::cout<<" START TEST #2!"<<std::endl; | std::cout<<" START TEST #2!"<<std::endl; | ||
Строка 193: | Строка 176: | ||
std::cout<<" End MAIN"<<std::endl; | std::cout<<" End MAIN"<<std::endl; | ||
return 0; | return 0; | ||
− | } | + | }</source> |
Приведу результаты выполнения двух тестов в SLAX 6.0.3: | Приведу результаты выполнения двух тестов в SLAX 6.0.3: | ||
− | START TEST #2! | + | <pre>START TEST #2! |
Enter N:222 | Enter N:222 | ||
Start test thread! | Start test thread! | ||
Строка 212: | Строка 195: | ||
Destructor done! Name:agent | Destructor done! Name:agent | ||
#-> END del_ptr_checker! | #-> END del_ptr_checker! | ||
− | End MAIN | + | End MAIN</pre> |
− | Как мы видим, функция del_ptr_checker() вызывается | + | Как мы видим, функция del_ptr_checker() вызывается независимо от значения параметра N, задаваемого пользователем, и динамическая память, занимаемая объектом класса checker, всегда корректно освобождается. |
− | + | ||
− | + | ||
− | + | ||
А вот что происходит в OpenSUSE 10.1: | А вот что происходит в OpenSUSE 10.1: | ||
− | linux@linux:~/super> ./test_2 | + | <pre>linux@linux:~/super> ./test_2 |
Enter N:654 | Enter N:654 | ||
End MAIN | End MAIN | ||
Строка 224: | Строка 204: | ||
Enter N:8888 | Enter N:8888 | ||
End MAIN | End MAIN | ||
− | linux@linux:~/super> | + | linux@linux:~/super></pre> |
Любопытно, но судя по выводу на консоль, поток, использующий | Любопытно, но судя по выводу на консоль, поток, использующий | ||
− | динамическую переменную класса checker, даже не создается, не | + | динамическую переменную класса checker, даже не создается, не говоря уже о вызове деструктора для объекта. Очень интересный результат! |
− | + | ||
Кстати, аналогичное поведение наблюдается и в Mpentoo Linux 2006.1. | Кстати, аналогичное поведение наблюдается и в Mpentoo Linux 2006.1. | ||
− | === Я тебя | + | === Я тебя породил… === |
Бывают ситуации, когда одному потоку нужно завершить другой: | Бывают ситуации, когда одному потоку нужно завершить другой: | ||
это может делаться при организации управления программой | это может делаться при организации управления программой | ||
или для экономии ограниченных ресурсов. В библиотеке Pthreads | или для экономии ограниченных ресурсов. В библиотеке Pthreads | ||
− | для этих целей предназначена функция pthread_cancel(). В | + | для этих целей предназначена функция pthread_cancel(). В качестве параметра она принимает идентификатор потоковой функции, которую надо завершить, и возвращает 0 в случае успешного выполнения. В уже упоминавшемся документе The Open Group |
− | + | ||
− | + | ||
− | + | ||
Base Specifications Issue 6 IEEE Std 1003.1, 2004 Edition в разделе, | Base Specifications Issue 6 IEEE Std 1003.1, 2004 Edition в разделе, | ||
описывающем функцию pthread_cancel(), говорится: «Функция | описывающем функцию pthread_cancel(), говорится: «Функция | ||
pthread_cancel() создает запрос на отмену потока. Когда он будет | pthread_cancel() создает запрос на отмену потока. Когда он будет | ||
реализован, зависит от текущего состояния потока и его типа. При | реализован, зависит от текущего состояния потока и его типа. При | ||
− | отмене потока должны быть вызваны обработчики, которые | + | отмене потока должны быть вызваны обработчики, которые выполнят связанные с отменой подготовительные действия. По завершению последнего обработчика должны быть вызваны деструкторы |
− | + | ||
− | + | ||
данных, используемых потоком». | данных, используемых потоком». | ||
− | Посмотрим, что же действительно происходит в данной | + | Посмотрим, что же действительно происходит в данной ситуации, с помощью следующей простой программы: |
− | + | ||
<source lang="cpp">void *task1(void *X){ | <source lang="cpp">void *task1(void *X){ | ||
std::cout<<" Start thread!"<<std::endl; | std::cout<<" Start thread!"<<std::endl; | ||
Строка 283: | Строка 256: | ||
второй имеет класс auto. Для уничтожения первого экземпляра мы | второй имеет класс auto. Для уничтожения первого экземпляра мы | ||
опять используем стек очистительно-восстановительных операций. | опять используем стек очистительно-восстановительных операций. | ||
− | Вызов pthread_setcancelstate() разрешает аннулирование нашего | + | Вызов pthread_setcancelstate() разрешает аннулирование нашего потока другим. Функция pthread_testcancel() проверяет наличие необработанных запросов на уничтожение. Если они есть, процесс аннулирования активизируется в точке вызова pthread_testcancel(). Обратите внимание, что в потоковой функции не используются разделяемые переменные и не происходит вызова системных функций (кроме вывода |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
сообщений на консоль), что обеспечивает ее безопасное прекращение. | сообщений на консоль), что обеспечивает ее безопасное прекращение. | ||
Строка 319: | Строка 287: | ||
* Тест № 1. При запуске программы в OpenSUSE 10.1 наблюдается порядок вызова деструкторов, отличный от всех других систем, получивших +. Однако все деструкторы вызываются, и утечки памяти не происходит. | * Тест № 1. При запуске программы в OpenSUSE 10.1 наблюдается порядок вызова деструкторов, отличный от всех других систем, получивших +. Однако все деструкторы вызываются, и утечки памяти не происходит. | ||
* Тест № 2. В двух системах, отмеченных ?, наблюдалась неустойчивая работа теста. Она проявлялась в том, что в одном случае результат теста был «правильным», и вывод на консоль полностью соответствовал ожидаемому, а в другом случае наблюдалась проблема с запуском потоковой функции. | * Тест № 2. В двух системах, отмеченных ?, наблюдалась неустойчивая работа теста. Она проявлялась в том, что в одном случае результат теста был «правильным», и вывод на консоль полностью соответствовал ожидаемому, а в другом случае наблюдалась проблема с запуском потоковой функции. | ||
+ | |||
+ | {| style="background:white;color:black;" border="1" cellspacing="0" align="center" | ||
+ | |+ Сводная таблица результатов тестирования | ||
+ | !rowspan="2"| № | ||
+ | !rowspan="2"| Операционная система | ||
+ | !colspan="3"| Номер теста | ||
+ | |- | ||
+ | !1 || 2|| 3 | ||
+ | |- | ||
+ | !1 | ||
+ | | | ||
+ | ; Mandriva 2008 KDE LiveCD | ||
+ | : Ядро: 2.6.22 | ||
+ | : GCC: 4.2.2* | ||
+ | : Glibc: 2.6.1 | ||
+ | : Libstdc++: 5.0.7 / 6.0.9 | ||
+ | | + ||?|| + | ||
+ | |- | ||
+ | !2 | ||
+ | | | ||
+ | ; Fedora 8 LiveCD | ||
+ | : Ядро: 2.6.23 | ||
+ | : GCC: 4.1.2* | ||
+ | : Glibc: 2.7 | ||
+ | : Libstdc++: 6.0.8 | ||
+ | | + || X|| + | ||
+ | |- | ||
+ | !3 | ||
+ | | | ||
+ | ; ASP Linux 11 LiveCD | ||
+ | : Ядро: 2.6.14 | ||
+ | : GCC: 4.0.2 | ||
+ | : Glibc: 2.3.5 | ||
+ | : Libstdc++: 5.0.7 / 6.0.7 | ||
+ | | + || X|| X | ||
+ | |- | ||
+ | !4 | ||
+ | | | ||
+ | ; Knoppix 5.3.1 LiveDVD | ||
+ | : Ядро: 2.6.24 | ||
+ | : GCC: 4.2.3 | ||
+ | : Glibc: 2.7 | ||
+ | : Libstdc++: 5.0.7 / 6.0.10 | ||
+ | | + || +|| + | ||
+ | |- | ||
+ | !5 | ||
+ | | | ||
+ | ; SLAX 6.0.3 LiveCD | ||
+ | : Ядро: 2.6.24 | ||
+ | : GCC: 4.2.3 | ||
+ | : Glibc: 2.7 | ||
+ | : Libstdc++: 6.0.8 / 6.0.9 | ||
+ | | +|| +|| + | ||
+ | |- | ||
+ | !6 | ||
+ | | | ||
+ | ; Ubuntu 8.0.4 LiveDVD | ||
+ | : Ядро: 2.6.24 | ||
+ | : GCC: 4.2.3 | ||
+ | : Glibc: 2.7 | ||
+ | : Libstdc++: 6.0.9 | ||
+ | | + || +|| + | ||
+ | |- | ||
+ | !7 | ||
+ | | | ||
+ | ; Ubuntu 7.10 LiveCD | ||
+ | : Ядро: 2.6.22 | ||
+ | : GCC: 4.1.3 | ||
+ | : Glibc: 2.6.1 | ||
+ | : Libstdc++: 6.0.9 | ||
+ | | + || ?|| X | ||
+ | |- | ||
+ | !8 | ||
+ | | | ||
+ | ; ALT Linux 3.0.4 LiveCD | ||
+ | : Ядро: 2.6.12 | ||
+ | : GCC: 3.4.4* | ||
+ | : Glibc: 2.3.5 | ||
+ | : Libstdc++: 5.0.7 / 6.0.3 | ||
+ | |X||X|| X | ||
+ | |- | ||
+ | !9 | ||
+ | | | ||
+ | ; ALT Linux 4.0.3 | ||
+ | : Ядро: 2.6.18 | ||
+ | : GCC: 4.1.1 | ||
+ | : Glibc: 2.5 | ||
+ | : Libstdc++: 5.0.7 / 6.0.8 | ||
+ | | + || X|| X | ||
+ | |- | ||
+ | !10 | ||
+ | | | ||
+ | ; MPentoo 2006.1 LiveCD | ||
+ | : Ядро: 2.6.16 | ||
+ | : GCC: 3.3.6 | ||
+ | : Glibc: 2.3.6 | ||
+ | : Libstdc++: 5.0.7 | ||
+ | |X|| X|| X | ||
+ | |- | ||
+ | !11 | ||
+ | | | ||
+ | ; Puppy Linux (rus_100) LiveCD | ||
+ | : Ядро: 2.6.21 | ||
+ | : GCC: 4.1.2* | ||
+ | : Glibc: 2.5 | ||
+ | : Libstdc++: 5.0.6 / 6.0.8 | ||
+ | | + || X|| X | ||
+ | |- | ||
+ | !12 | ||
+ | | | ||
+ | ; Gentoo 2008 Beta2 LiveCD | ||
+ | : Ядро: 2.6.24 | ||
+ | : GCC: 4.1.2 | ||
+ | : Glibc: 2.6.1 | ||
+ | : Libstdc++: 6.0.8 | ||
+ | | + || +|| + | ||
+ | |- | ||
+ | !13 | ||
+ | | | ||
+ | ; OpenSUSE 10.1 LiveDVD | ||
+ | : Ядро: 2.6.16 | ||
+ | : GCC: 4.1.0* | ||
+ | : Glibc: 2.4 | ||
+ | : Libstdc++: 5.0.7 / 6.0.8 | ||
+ | | ? || X ||X | ||
+ | |} | ||
+ | {| align="center" | ||
+ | | Обозначения: | ||
+ | : X — тест выполнен с ошибками, результат не соответствует ожиданиям | ||
+ | : ? — неожиданный результат выполнения теста, интересен для анализа | ||
+ | : + — тест успешно выполнен, результат адекватен ожиданиями | ||
+ | : * — на диске компилятор отсутствует, проверка на бинарной сборке из SLAX | ||
+ | |} | ||
=== Что в итоге? === | === Что в итоге? === | ||
− | Какие же выводы можно сделать на основании результатов, | + | Какие же выводы можно сделать на основании результатов, отраженных в таблице? Во-первых, при разработке многопоточных |
− | + | ||
приложений с использованием библиотеки Pthreads программист | приложений с использованием библиотеки Pthreads программист | ||
должен уделять повышенное внимание тем фрагментам кода, | должен уделять повышенное внимание тем фрагментам кода, | ||
где используются объекты классов. Несмотря на то, что Pthreads | где используются объекты классов. Несмотря на то, что Pthreads | ||
− | имеет средство автоматического освобождения ресурсов | + | имеет средство автоматического освобождения ресурсов — стек |
− | очистительно-восстановительных операций, гарантировать | + | очистительно-восстановительных операций, гарантировать обязательность и правильность его использования нельзя. В описании |
− | + | многих функций библиотеки сообщается, что при завершении потока сначала вызываются процедуры из очистительного стека, а потом | |
− | многих функций библиотеки сообщается, что при завершении | + | |
− | + | ||
деструкторы потоковых данных, а на самом деле это не всегда так. | деструкторы потоковых данных, а на самом деле это не всегда так. | ||
Во-вторых, разработчики Linux-систем вполне осведомлены об | Во-вторых, разработчики Linux-систем вполне осведомлены об | ||
− | особенностях поведения функций библиотеки Pthreads при | + | особенностях поведения функций библиотеки Pthreads при использовании в качестве данных объектов классов. Ведется активная |
− | + | ||
работа в этом направлении, и положительные результаты есть! | работа в этом направлении, и положительные результаты есть! | ||
Показателен пример Ubuntu. | Показателен пример Ubuntu. | ||
Строка 341: | Строка 438: | ||
В-третьих, анализ характеристик дистрибутивов, получивших + | В-третьих, анализ характеристик дистрибутивов, получивших + | ||
по всем тестам, обнаруживает, что версия ядра (в этих тестовых | по всем тестам, обнаруживает, что версия ядра (в этих тестовых | ||
− | образцах) не ниже 2.6.24 (это обязательное условие), | + | образцах) не ниже 2.6.24 (это обязательное условие), библиотека glibc — не ниже 2.6.1 (лучше 2.7), библиотека libstdc++ — не |
− | + | ниже 6.0.8 (лучше 6.0.9), версия компилятора GCC — не ниже 4.1.2. | |
− | ниже 6.0.8 (лучше 6.0.9), версия компилятора GCC | + | |
Соответственно, дистрибутивы, получившие за тест -, имеют другие | Соответственно, дистрибутивы, получившие за тест -, имеют другие | ||
− | версии библиотек. Например, в ALT Linux 4.0.3 используется | + | версии библиотек. Например, в ALT Linux 4.0.3 используется библиотека glibc версии 2.5. Кстати, очень интересно сравнить результаты |
− | + | Gentoo 2008 Beta 2 и Fedora Core 8. Исход их «спора» решила версия ядра. У Gentoo она выше, хотя у Fedora библиотека glibc новее. | |
− | Gentoo 2008 Beta 2 и Fedora Core 8. Исход их «спора» решила | + | |
− | + | ||
Это говорит о том, что своевременное обновление ядра и ключевых | Это говорит о том, что своевременное обновление ядра и ключевых | ||
− | системных библиотек позволяет повысить надежность работы | + | системных библиотек позволяет повысить надежность работы операционной системы. |
− | + | ||
− | Читателя наверняка интересует вопрос: а что будет, если | + | Читателя наверняка интересует вопрос: а что будет, если программу, скомпилированную в «правильной» системе, например, |
− | + | ||
Knoppix 5.3.1, попробовать запустить в «неправильной», скажем, | Knoppix 5.3.1, попробовать запустить в «неправильной», скажем, | ||
Knoppix 3.2 RE? Здесь возможны два варианта: | Knoppix 3.2 RE? Здесь возможны два варианта: | ||
Строка 364: | Строка 456: | ||
=== И что делать? === | === И что делать? === | ||
+ | {{Врезка | ||
+ | |Заголовок=Скорая помощь | ||
+ | |Содержание=Узнать параметры своей | ||
+ | системы можно, | ||
+ | набрав в консоли следующие | ||
+ | команды: | ||
+ | * Версия ядра: | ||
+ | uname -a | ||
+ | * Компилятор GCC: | ||
+ | gcc --version | ||
+ | * Библиотека glibc : | ||
+ | getconf GNU_LIBC_VERSION | ||
+ | * Библиотека libstdc++: | ||
+ | ls -l /usr/lib/libstdc++.so.* | ||
+ | |Ширина=250px}} | ||
Что можно посоветовать, чтобы свести к минимуму издержки, связанные с особенностями взаимодействия библиотеки Pthreads с | Что можно посоветовать, чтобы свести к минимуму издержки, связанные с особенностями взаимодействия библиотеки Pthreads с | ||
объектами классов? | объектами классов? | ||
− | Что касается теста | + | Что касается теста № 1, то тут может помочь метафора «песочницы»: прием, при котором вся работа с объектами классов, имеющих тип памяти auto (не динамические, а «обычные» переменные), ведется в пределах блока, выделенного в тексте программы |
фигурными скобками {…}. При выходе из блока происходит автоматический вызов деструкторов, после чего можно «запускать» | фигурными скобками {…}. При выходе из блока происходит автоматический вызов деструкторов, после чего можно «запускать» | ||
pthread_exit(). | pthread_exit(). | ||
Строка 377: | Строка 484: | ||
трудно и хлопотно, однако на текущий момент это, наверное, единственный эффективный выход из ситуации. Кстати, для объектов с | трудно и хлопотно, однако на текущий момент это, наверное, единственный эффективный выход из ситуации. Кстати, для объектов с | ||
классом памяти auto деструктор можно вызвать как обычную функцию. | классом памяти auto деструктор можно вызвать как обычную функцию. | ||
+ | |||
+ | [[Категория:Андрей Кузьменко]] |
Текущая версия на 19:56, 3 июня 2015
|
|
|
Содержание |
[править] Дырки в паутине
- Утечка ресурсов — в первую очередь, памяти — одна из проблем современных сложных приложений. Андрей Кузьменко покажет, как избежать ее в ваших программах.
Можно смело считать 2007 год переломным моментом в переходе на многоядерные процессоры и активном внедрении многопоточного программирования в повседневную практику. Компании Intel и AMD обозначили производство многоядерных процессоров как основное направление своего развития на ближайшие несколько лет. Покупая современный ноутбук или ПК, вы обязательно увидите шильдик Core2Duo или Athlon X2. В текущем году производители чипов уже делают упор на четырехъядерные процессоры. Очевидно, что производители ПО должны адекватно реагировать на эти изменения, выпуская продукты, задействующие все преимущества новых технологий. А на чем они пишутся?
- Метамодернизм в позднем творчестве В.Г. Сорокина
- ЛитРПГ - последняя отрыжка постмодерна
- "Ричард III и семиотика"
- 3D-визуализация обложки Ridero создаем обложку книги при работе над самиздатом.
- Архитектура метамодерна - говоря о современном искусстве, невозможно не поговорить об архитектуре. В данной статье будет отмечено несколько интересных принципов, характерных для построек "новой волны", столь притягательных и скандальных.
- Литература
- Метамодерн
- Рокер-Прометей против изначального зла в «Песне про советскую милицию» Вени Дркина, Автор: Нина Ищенко, к.ф.н, член Союза Писателей ЛНР - перепубликация из журнала "Топос".
- Как избавиться от комаров? Лучшие типы ловушек.
- Что делать если роблокс вылетает на windows
- Что делать, если ребенок смотрит порно?
- Почему собака прыгает на людей при встрече?
- Какое масло лить в Задний дифференциал (мост) Visco diff 38434AA050
- О чем может рассказать хвост вашей кошки?
- Верветки
- Отчетность бюджетных учреждений при закупках по Закону № 223-ФЗ
- Срок исковой давности как правильно рассчитать
- Дмитрий Патрушев минсельхоз будет ли преемником Путина
- Кто такой Владислав Поздняков? Что такое "Мужское Государство" и почему его признали экстремистским в России?
- Как правильно выбрать машинное масло в Димитровграде?
- Как стать богатым и знаменитым в России?
- Почему фильм "Пипец" (Kick-Ass) стал популярен по всему миру?
- Как стать мудрецом?
- Как правильно установить FreeBSD
- Как стать таким как Путин?
- Где лучше жить - в Димитровграде или в Ульяновске?
- Почему город Димитровград так называется?
- Что такое метамодерн?
- ВАЖНО! Временное ограничение движения автотранспортных средств в Димитровграде
- Тарифы на электроэнергию для майнеров предложено повысить
В языке C++, в отличие, например, от Java, отсутствует встроенная поддержка многопоточного программирования. Иными словами, с помощью «голого» C++ нельзя создавать соответствующие стандарту языка многопоточные приложения. Разумеется, данная функциональность все же доступна в виде библиотек, например, Pthreads (POSIX Threads), особенной популярной в мире Unix. Кроме нее, разработчик может использовать библиотеку Boost (http://www.boost.org) или ThreadWeaver (http://api.kde.org). Коммерческие Unix-системы, например, Sun Solaris, предлагают собственные библиотеки многопоточного программирования.
На данном уроке мы рассмотрим некоторые проблемы, возникающие при написании программ на C++ с использованием библиотеки Pthreads. Наряду со встроенными базовыми типами (int, char, double) в функциях работы с потоками могут использоваться объекты классов. Вот тут-то нас и подстерегают проблемы и неожиданности, которые мы сегодня обсудим. Кроме этого, мы оценим работу многопоточных программ, написанных на C++ и Pthreads, в различных Linux-системах.
[править] И снова классы…
Одной из серьезных проблем языка C++ (настолько серьезной, что иные компании создают целые платформы со сборкой мусора, лишь бы с нею не сталкиваться) является утечка ресурсов. Это может быть память, выделенная с помощью оператора new, файловые дескрипторы, сетевые сокеты, мьютексы. Для освобождения ресурсов, используемых объектом, в C++ предусмотрены специальные методы — деструкторы. Задача программиста — правильно написать деструктор и убедиться в том, что в программе происходит его вызов. Только так можно гарантировать, что ресурсы будут возвращены системе.
Во всех тестах, описываемых в этой статье, будет использоваться класс checker, объявленный в файле checker.hpp. Вот он:
class checker{ private: string name; public: explicit checker(string s) ; ~checker( ); void calc(long N); void say_hello(void); };
Класс обладает «говорящими» конструктором и деструктором, метод calc() имитирует длительную по времени расчетную задачу, а функция say_hello() выводит сообщение на консоль.
[править] С вещами на выход!
Выполнение потоковой функции может быть прервано по трем причинам:
- В результате «естественного завершения» оператором return;
- В результате вызова функции pthread_exit();
- В результате аннулирования другим потоком.
Особый интерес для нас будет представлять вызов деструкторов объектов в случаях 2 и 3.
В документе The Open Group Base Specifications Issue 6 IEEE Std 1003.1, 2004 Edition в разделе, посвященном функции pthread_exit(), по этому поводу говорится следующее [здесь и далее перевод автора]: «Функция void pthread_exit(void *value_ptr) завершает вызывающий поток и делает значение value_ptr доступным для успешного присоединения к завершающему потоку. Любые обработчики отмены, помещенные в стек, но еще не извлеченные из него, будут извлечены в порядке, обратном по отношению к порядку помещения в стек, а после выполнены. Если потоку принадлежат данные, то после выполнения всех обработчиков отмены будут вызваны соответствующие функции деструкторов, при этом порядок их вызова не определен. При завершении потока ресурсы процесса, включая мьютексы и дескрипторы файлов, не освобождаются, и не выполняются никакие восстановительные действия уровня процесса, включая всевозможные вызовы любых функций atexit()». То есть, если объект в потоковой функции представляет собой локальную переменную с классом памяти auto, то после вызова pthread_exit() для него должен быть автоматически выполнен деструктор. Однако, как показывает практика, бывают случаи, когда этого не происходит.
Рассмотрим следующую программу:
void* task1(void *X) { std::cout<<" Start task_1!"<<std::endl; checker P("First"); P.calc(5); pthread_exit(NULL); return 0; } void* task2(void *X) { std::cout<<" Start task_2!"<<std::endl; checker Q("Second"); Q.calc(8); return 0; } int main(void){ std::cout<<" Start test #1!"<<std::endl; pthread_t threadA, threadB; pthread_create(&threadA, NULL, task1, NULL); pthread_detach(threadA); pthread_create(&threadB, NULL, task2, NULL); pthread_detach(threadB); pthread_exit(NULL); return 0; }
Здесь объявлены две потоковых функции: task1() и task2(). В качестве элемента данных в каждой из них используется объект класса checker, выделенный в стеке. Функция task1() завершается принудительно с помощью pthread_exit(), а task2() выходит «естественным образом» через return 0. При этом потоки создаются как открепленные, то есть при их уничтожении ресурсы, которые они использовали, сразу же возвращаются системе. Рассмотрим результат работы программы в различных дистрибутивах Linux. Например, в SLAX 6.0.3 вывод на консоль будет таким:
Start test #1! Start task_1! Constructor done! Name:First Start task_2! Constructor done! Name:Second Destructor done! Name:First Destructor done! Name:Second
А вот что получается в ALT Linux 3.0.4:
Start test #1! Start task_1! Constructor done! Name:First Start task_2! Constructor done! Name:Second Destructor done! Name:Second
Видите? Деструктор объекта из потоковой функции task1() вызван не был! Конечно, 3.0.4 — не самая актуальная версия данного дистрибутива, но, как мы увидим далее, аналогичные проблемы имеют место и в более современных ОС.
[править] Поел — убери за собой
Очень часто при завершении потока бывает необходимо выполнить некоторые заключительные операции: освободить память, закрыть файлы, снять блокировки с разделяемых переменных и т.п. Желательно, чтобы эти действия выполнялись единообразно, как для стандартного завершения потоковой функции оператором return, так и при аннулировании другим потоком. Библиотека Pthreads предоставляет для этого возможность, называемую «стеком очистительно-восстановительных операций». Как она работает? С каждым потоком, имеющимся в программе, связывается стек очистительно-восстановительных операций, который содержит указатели на функции, вызываемые во время аннулирования (завершения) потока. Для работы с данным стеком используются две функции (или макроса):
- pthread_cleanup_push() Принимает в качестве параметров указатель на помещаемую в стек функцию и передаваемый ей аргумент;
- pthread_cleanup_pop() Принимает в качестве параметра целочисленное значение и извлекает завершающую функцию с вершины стека. Если аргумент отличен от нуля, завершающая функция выполняется.
Давайте рассмотрим еще один пример. Здесь мы определяем потоковую функцию, которая в зависимости от значения параметра, заданного пользователем, завершается либо «обычным образом», либо вызовом pthread_exit():
void* task1(void *X){ std::cout<<" Start test thread!"<<std::endl; checker *Z = new checker("agent"); pthread_cleanup_push(del_ptr_checker, Z); int *counter = static_cast<int*>(X); for(int i=0; i<(*counter); ++i) { if(i==1000) pthread_exit(NULL); } pthread_cleanup_pop(1); std::cout<<" Thread go boom!"<<std::endl; return 0; }
Обратите внимание, что в качестве элемента данных теперь используется экземпляр класса checker, расположенный в динамической памяти. Для ее освобождения была написана функция del_ptr_checker(), указатель на которую помещается в стек очистительно-восстановительных операций. Мы вызываем деструктор объекта при помощи pthread_cleanup_pop(1) — кажется, это должно гарантировать выполнение очистительных действий вне зависимости от способа завершения потока. Функция del_ptr_checker() сама по себе довольно проста:
void del_ptr_checker(void *X){ checker *del = static_cast<checker*>(X); std::cout<<" #-> START del_ptr_checker!"<<std::endl; delete del; del = 0; std::cout<<" #-> END del_ptr_checker!"<<std::endl; }
Мы приводим переданный указатель к типу checker * и вызываем оператор delete для освобождения памяти. del_ptr_checker() определена в файле helper.hpp. Функция main() для нашего примера выглядит так:
int main(void) { std::cout<<" START TEST #2!"<<std::endl; int N=0; std::cout<<" Enter N:"; std::cin>>N; pthread_t threadA; pthread_create(&threadA, NULL, task1, &N); pthread_detach(threadA); std::cout<<" End MAIN"<<std::endl; return 0; }
Приведу результаты выполнения двух тестов в SLAX 6.0.3:
START TEST #2! Enter N:222 Start test thread! Constructor done! Name:agent #-> START del_ptr_checker! Destructor done! Name:agent #-> END del_ptr_checker! Thread go boom! End MAIN START TEST #2! Enter N:4589 Start test thread! Constructor done! Name:agent #-> START del_ptr_checker! Destructor done! Name:agent #-> END del_ptr_checker! End MAIN
Как мы видим, функция del_ptr_checker() вызывается независимо от значения параметра N, задаваемого пользователем, и динамическая память, занимаемая объектом класса checker, всегда корректно освобождается. А вот что происходит в OpenSUSE 10.1:
linux@linux:~/super> ./test_2 Enter N:654 End MAIN linux@linux:~/super> ./test_2 Enter N:8888 End MAIN linux@linux:~/super>
Любопытно, но судя по выводу на консоль, поток, использующий динамическую переменную класса checker, даже не создается, не говоря уже о вызове деструктора для объекта. Очень интересный результат! Кстати, аналогичное поведение наблюдается и в Mpentoo Linux 2006.1.
[править] Я тебя породил…
Бывают ситуации, когда одному потоку нужно завершить другой: это может делаться при организации управления программой или для экономии ограниченных ресурсов. В библиотеке Pthreads для этих целей предназначена функция pthread_cancel(). В качестве параметра она принимает идентификатор потоковой функции, которую надо завершить, и возвращает 0 в случае успешного выполнения. В уже упоминавшемся документе The Open Group Base Specifications Issue 6 IEEE Std 1003.1, 2004 Edition в разделе, описывающем функцию pthread_cancel(), говорится: «Функция pthread_cancel() создает запрос на отмену потока. Когда он будет реализован, зависит от текущего состояния потока и его типа. При отмене потока должны быть вызваны обработчики, которые выполнят связанные с отменой подготовительные действия. По завершению последнего обработчика должны быть вызваны деструкторы данных, используемых потоком».
Посмотрим, что же действительно происходит в данной ситуации, с помощью следующей простой программы:
void *task1(void *X){ std::cout<<" Start thread!"<<std::endl; checker Q("spy"); checker *Z = new checker("agent"); pthread_cleanup_push(del_ptr_checker, Z); pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); std::cout<<" @-> Hello, Threads!"<<std::endl; for(int i=0; i<5; ++i) { Z->say_hello( ); sleep(1); } pthread_testcancel(); pthread_cleanup_pop(1); std::cout<<" @-> Logical End of THREAD function"<<std::endl; } int main(int argc, char * argv[]){ cout<<" Start test #3!"<<std::endl; int ret; pthread_t thread; pthread_create(&thread, NULL, task1, NULL); ret = pthread_cancel(thread); if(ret==0) { std::cout<<" $$$ Thread CANCEL OK!"<<std::endl; } pthread_join(thread, NULL); std::cout<<" $$$ The thread go boom!"<<std::endl; return 0; }
В качестве элемента данных потоковая функция task1() использует два объекта класса checker: один расположен в динамической памяти, второй имеет класс auto. Для уничтожения первого экземпляра мы опять используем стек очистительно-восстановительных операций. Вызов pthread_setcancelstate() разрешает аннулирование нашего потока другим. Функция pthread_testcancel() проверяет наличие необработанных запросов на уничтожение. Если они есть, процесс аннулирования активизируется в точке вызова pthread_testcancel(). Обратите внимание, что в потоковой функции не используются разделяемые переменные и не происходит вызова системных функций (кроме вывода сообщений на консоль), что обеспечивает ее безопасное прекращение.
Результат выполнения тестовой программы в SLAX 6.0.3 таков:
Start test #3! Start thread! Constructor done! Name:spy Constructor done! Name:agent @-> Hello, Threads! Hello from cheker! #-> START del_ptr_checker! Destructor done! Name:agent #-> END del_ptr_checker! Destructor done! Name:spy $$$ Thread CANCEL OK! $$$ The thread go boom!
А вот вывод в системе Mpentoo 2006.1:
Start test #3! $$$ Thread CANCEL OK! Start thread! $$$ The thread go boom!
Мы видим, что работа потоковой функции, судя по выводу на консоль, завершилась раньше ее начала. При этом объекты класса checker в потоковой функции не создаются.
Давайте проанализируем полученные результаты: для удобства они сведены в таблицу. Ситуации, отмеченные знаком ?, очевидно, нуждаются в комментариях.
- Тест № 1. При запуске программы в OpenSUSE 10.1 наблюдается порядок вызова деструкторов, отличный от всех других систем, получивших +. Однако все деструкторы вызываются, и утечки памяти не происходит.
- Тест № 2. В двух системах, отмеченных ?, наблюдалась неустойчивая работа теста. Она проявлялась в том, что в одном случае результат теста был «правильным», и вывод на консоль полностью соответствовал ожидаемому, а в другом случае наблюдалась проблема с запуском потоковой функции.
№ | Операционная система | Номер теста | ||
---|---|---|---|---|
1 | 2 | 3 | ||
1 |
|
+ | ? | + |
2 |
|
+ | X | + |
3 |
|
+ | X | X |
4 |
|
+ | + | + |
5 |
|
+ | + | + |
6 |
|
+ | + | + |
7 |
|
+ | ? | X |
8 |
|
X | X | X |
9 |
|
+ | X | X |
10 |
|
X | X | X |
11 |
|
+ | X | X |
12 |
|
+ | + | + |
13 |
|
? | X | X |
Обозначения:
|
[править] Что в итоге?
Какие же выводы можно сделать на основании результатов, отраженных в таблице? Во-первых, при разработке многопоточных приложений с использованием библиотеки Pthreads программист должен уделять повышенное внимание тем фрагментам кода, где используются объекты классов. Несмотря на то, что Pthreads имеет средство автоматического освобождения ресурсов — стек очистительно-восстановительных операций, гарантировать обязательность и правильность его использования нельзя. В описании многих функций библиотеки сообщается, что при завершении потока сначала вызываются процедуры из очистительного стека, а потом деструкторы потоковых данных, а на самом деле это не всегда так.
Во-вторых, разработчики Linux-систем вполне осведомлены об особенностях поведения функций библиотеки Pthreads при использовании в качестве данных объектов классов. Ведется активная работа в этом направлении, и положительные результаты есть! Показателен пример Ubuntu.
В-третьих, анализ характеристик дистрибутивов, получивших + по всем тестам, обнаруживает, что версия ядра (в этих тестовых образцах) не ниже 2.6.24 (это обязательное условие), библиотека glibc — не ниже 2.6.1 (лучше 2.7), библиотека libstdc++ — не ниже 6.0.8 (лучше 6.0.9), версия компилятора GCC — не ниже 4.1.2. Соответственно, дистрибутивы, получившие за тест -, имеют другие версии библиотек. Например, в ALT Linux 4.0.3 используется библиотека glibc версии 2.5. Кстати, очень интересно сравнить результаты Gentoo 2008 Beta 2 и Fedora Core 8. Исход их «спора» решила версия ядра. У Gentoo она выше, хотя у Fedora библиотека glibc новее. Это говорит о том, что своевременное обновление ядра и ключевых системных библиотек позволяет повысить надежность работы операционной системы.
Читателя наверняка интересует вопрос: а что будет, если программу, скомпилированную в «правильной» системе, например, Knoppix 5.3.1, попробовать запустить в «неправильной», скажем, Knoppix 3.2 RE? Здесь возможны два варианта:
- Программа не запустится из-за отсутствия необходимых библиотек и выдаст сообщение следующего содержания:
-
knoppix@ttyp0[knoppix]$ ./etalon
-
./etalon: error while loading shared libraries: libstdc++.so.6: cannot open shared object file: No such file or directory
-
knoppix@ttyp0[knoppix]$
-
- Однако, даже если «правильная» программа запустится в «неправильной» среде, вести себя она будет «неправильно».
[править] И что делать?
- Метамодернизм в позднем творчестве В.Г. Сорокина
- ЛитРПГ - последняя отрыжка постмодерна
- "Ричард III и семиотика"
- 3D-визуализация обложки Ridero создаем обложку книги при работе над самиздатом.
- Архитектура метамодерна - говоря о современном искусстве, невозможно не поговорить об архитектуре. В данной статье будет отмечено несколько интересных принципов, характерных для построек "новой волны", столь притягательных и скандальных.
- Литература
- Метамодерн
- Рокер-Прометей против изначального зла в «Песне про советскую милицию» Вени Дркина, Автор: Нина Ищенко, к.ф.н, член Союза Писателей ЛНР - перепубликация из журнала "Топос".
- Как избавиться от комаров? Лучшие типы ловушек.
- Что делать если роблокс вылетает на windows
- Что делать, если ребенок смотрит порно?
- Почему собака прыгает на людей при встрече?
- Какое масло лить в Задний дифференциал (мост) Visco diff 38434AA050
- О чем может рассказать хвост вашей кошки?
- Верветки
- Отчетность бюджетных учреждений при закупках по Закону № 223-ФЗ
- Срок исковой давности как правильно рассчитать
- Дмитрий Патрушев минсельхоз будет ли преемником Путина
- Кто такой Владислав Поздняков? Что такое "Мужское Государство" и почему его признали экстремистским в России?
- Как правильно выбрать машинное масло в Димитровграде?
- Как стать богатым и знаменитым в России?
- Почему фильм "Пипец" (Kick-Ass) стал популярен по всему миру?
- Как стать мудрецом?
- Как правильно установить FreeBSD
- Как стать таким как Путин?
- Где лучше жить - в Димитровграде или в Ульяновске?
- Почему город Димитровград так называется?
- Что такое метамодерн?
- ВАЖНО! Временное ограничение движения автотранспортных средств в Димитровграде
- Тарифы на электроэнергию для майнеров предложено повысить
Что можно посоветовать, чтобы свести к минимуму издержки, связанные с особенностями взаимодействия библиотеки Pthreads с объектами классов?
Что касается теста № 1, то тут может помочь метафора «песочницы»: прием, при котором вся работа с объектами классов, имеющих тип памяти auto (не динамические, а «обычные» переменные), ведется в пределах блока, выделенного в тексте программы фигурными скобками {…}. При выходе из блока происходит автоматический вызов деструкторов, после чего можно «запускать» pthread_exit().
Однако возникает вопрос: что делать, если pthread_exit() вызывается в результате выполнения некоторого условия при работе программы, как, например, в тесте № 2, или происходит аннулирование потока, как в тесте № 3? Здесь относительно универсальным, хотя и достаточно трудозатратным будет такой выход, как «ручное» управление памятью посредством операторов new и delete. Да, это трудно и хлопотно, однако на текущий момент это, наверное, единственный эффективный выход из ситуации. Кстати, для объектов с классом памяти auto деструктор можно вызвать как обычную функцию.