Новосибирский Государственный Университет
Опубликован: 28.04.2010 | Доступ: свободный | Студентов: 684 / 60 | Оценка: 4.62 / 4.24 | Длительность: 10:21:00
Лекция 3:

Создание и завершение нитей

< Лекция 2 || Лекция 3: 1234 || Лекция 4 >

2. Завершение нити

Для завершения нити используется функция pthread_exit(3C). Эта функция всегда завершается успешно и принимает единственный параметр, указатель на код возврата типа void *. Как и в случае с pthread_create(3C), библиотека никогда не пытается обращаться к значению этого параметра как к указателю, поэтому его можно использовать для передачи скалярных значений.

Другой способ завершения нити – это возврат управления из функции start_routine при помощи оператора return. Поскольку функция start_routine должна возвращать тип void *, все ее операторы return должны возвращать значение. Этот способ практически полностью эквивалентен вызову pthread_exit(3C) с тем же самым значением.

Примечание

При компиляции программ на С++ некоторыми версиями компиляторов (наиболее известны проблемы такого рода с GCC, но проблемы есть и у некоторых коммерческих компиляторов), при завершении нити вызовом pthread_exit(3C) не вызываются деструкторы локальных переменных. Информацию об этом не всегда удается найти в документации. В действительности это определяется не столько версией компилятора, сколько опциями при сборке компилятора и особенностями C runtime/libpthread. Так, компилятор Sun Studio 11 вызывает деструкторы, а GCC 3.4.3, входящий в поставку Solaris 10 – не вызывает.

GCC 2.95.4 в Debian Woody не вызывает деструкторы, GCC 3.3.5 в Debian Sarge – вызывает.

Intel C++ Compiler 7.1 при работе на Debian Woody не вызывает деструкторы.

Протестировать взаимодействие POSIX Thread API и вашего компилятора C++ можно при помощи программы из примера 3.2.

#include <pthread.h> 
#include <unistd.h> 
#include <iostream> 

class TestClass { 
  public:    
  int value; 
  TestClass(int parameter) { 
    std::cout << "Constructed " << parameter << "\n"; 
    value=parameter; 
  } 
  ~TestClass() { 
    std::cout << "destructed " << value << "\n"; 
  } 
}; 

extern "C" { 
  void *body_forexit(void * param) { 
    TestClass a(1); 
    sleep(1); 
    pthread_exit((void *)a.value); 
    return (void *)a.value; 
  } 

  void *body_forreturn(void * param) { 
    TestClass b(2); 
    sleep(2); 
    return (void *)b.value; 
  } 
} // extern "C" 

int main() { 
  pthread_t thread1, thread2; 
  
  pthread_create(&thread1, NULL, body_forexit, NULL); 
  pthread_detach(thread1); 
  pthread_create(&thread2, NULL, body_forreturn, NULL); 
  pthread_detach(thread2); 
  pthread_exit(NULL); 
  return 0; 
}
3.2.
Примечание 2

Завершение процесса системным вызовом exit(2) или возвратом из функции main приводит к завершению всех нитей процесса. Это поведение описано в стандарте POSIX, поэтому ОС, которые ведут себя иначе (например, старые версии Linux), не соответствуют стандарту. Если вы хотите, чтобы нити вашей программы продолжали исполнение после завершения main, следует завершать main при помощи вызова pthread_exit(3C).

Поведение деструкторов локальных переменных в нитях при завершении процесса при помощи exit(2) не описано в стандарте. На практике они не вызываются даже для переменных, описанных в основной нити программы, и даже в однопоточных программах (проверялось в Solaris и Linux на всех доступных версиях компиляторов).

В действительности, все еще хуже – при выходе по exit(2) начинают вызываться деструкторы статических и глобальных переменных, но нити продолжают работу – в некоторых случаях это может приводить к аварийному завершению программы. Так, если в программе примера 3.2 в строке 42 закомментировать pthread_exit, то программа, собранная Sun Studio 11, с высокой вероятностью завершается по Segmentation Fault (попытка вывода в std::cout после того, как деструктор std::cout уже отработал). Поэтому если вы хотите экстренно завершить многопоточную программу, написанную на C++, используйте _exit(2). Если вам необходимо обеспечить вызов деструкторов локальных переменных, следует использовать исключенияС++.

Хотя для ряда платформ обеспечивается удовлетворительное взаимодействие POSIX Thread library со средой исполнения С++, написание многоплатформенных многопоточных приложений на C++ может оказаться сложной задачей. Во многих случаях для написания переносимых многопоточных приложений на C++ лучше использовать не непосредственно POSIX Threads, а BOOST threads.

К передаче выходного значения нити относятся все те же самые соображения, что и к передаче параметров нити. Хотя выходное значение нити описано как void *, библиотека никогда не обращается к нему как к указателю, поэтому в качестве значения можно возвращать ноль или другие скалярные значения.

При передаче указателей возникают те же проблемы, что и при передаче параметров – опасность возникновения висячих ссылок и опасность утечки памяти. Ни в коем случае нельзя возвращать указатели на локальные переменные и другие объекты, созданные в стеке нити, потому что нить, получающая возвращаемое значение функцией pthread_join(3C) (рассматривается ниже), получает доступ к значению лишь после того, как стек нити и другие ресурсы, связанные с нитью, в том числе thread local data, уже уничтожены.

При использовании для размещения возвращаемого значения malloc(3C) возникает опасность утечки памяти. Действительно, если для размещения значения использовать malloc(3C), то нить, получающая наше значение, должна его освободить. Но если эта нить получит сигнал принудительного завершения ( pthread_cancel(3C) ), то она не дождется нашего завершения и не освободит наше значение.

Универсального решения этой проблемы не существует. В программах с фиксированным или ограниченным числом нитей часто используют для передачи параметров и выходных значений указатели на статические переменные. В программах с переменным числом нитей обычно используют структуру, когда одна нить создает все остальные нити, размещает для них блоки переменных состояния, дожидается завершения каждой из нитей и уничтожает или переиспользует эти блоки. При этом, разумеется, логично, чтобы нить получала указатель на свой блок переменных состояния как параметр, работала с ним в течение всего срока жизни и возвращала этот же блок в качестве параметра pthread_exit(3C). Но эта архитектура пригодна не для всех приложений.

< Лекция 2 || Лекция 3: 1234 || Лекция 4 >