Вот как может выглядеть в этой технике безопасный (с позиции возможной асинхронной отмены потока) захват мьютекса:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void cleanup(void* arg) { pthread_mutex_unlock(&mutex); }
void* thread_function(void* arg) {
while (true) {
pthread_mutex_lock(&mutex);
pthread_cleanup_push(&cleanup, NULL);
{
// все точки отмены должны быть расставлены в этом блоке!
}
pthread_testcancel();
pthread_cleanup_pop(1);
}
}
«Легковесность» потока
Вот теперь, завершив краткий экскурс использования процессов и потоков, можно вернуться к вопросу, который вскользь уже звучал по ходу рассмотрения: почему и в каком смысле потоки часто называют «легкими процессами» (LWP — lightweight process)?
Выполним ряд тестов по сравнительной оценке временных затрат на создание процесса и потока. Начнем с процесса ( файл p2-1.cc):
struct mbyte { // мегабайтный блок данных
#pragma pack(1)
uint8_t data[1024 * 1024];
#pragma pack(4)
};
int main(int argc, char *argv[]) {
mbyte *blk = NULL;
if (argc > 1 && atoi(argv[1]) > 0) {
blk = new mbyte[atoi(argv[1])];
}
uint64_t t = ClockCycles();
pid_t pid = fork();
if (pid == -1) perror("fork"), exit(EXIT_FAILURE);
if (pid == 0) exit(EXIT_SUCCESS);
if (pid > 0) {
waitpid(pid, NULL, WEXITED);
t = ClockCycles() - t;
}
if (blk != NULL) delete blk;
cout << "Fork time " << cycle2milisec(t)
<< " msec. [" << t << " cycles]" << endl; exit(EXIT_SUCCESS);
}
Эта программа сделана так, что может иметь один численный параметр: размер (в мегабайтах) блока условных данных (в нашем случае даже неинициализированных), принадлежащего адресному пространству процесса. (Функцию преобразования процессорных циклов в соответствующий миллисекундный интервал
cycle2milisec()
А теперь оценим временные затраты на создание клона процесса в зависимости от объема программы (мы сознательно использовали клонирование процесса вызовом
fork()
spawn*()
exec*()
# p2-1
fork time: 3.4333 msec. [1835593 cycles]
# p2-1 1
Fork time: 17.0706 msec [9126696 cycles]
# p2-1 2
Fork time: 31.5257 msec. [16855024 cycles]
# p2-1 5
Fork time: 70.7234 msec. [37811848 cycles]
# p2-1 20
Fork time: 264.042 msec. [141168680 cycles]
# p2-1 50
Fork time: 661.312 msec. [353566688 cycles]
# p2-1 100
Fork time: 1169.45 msec. [625241336 cycles]
Наблюдаются, во-первых, достаточно большие временные затраты на создание процесса (к этому мы еще вернемся), а во-вторых, близкая к линейной зависимость времени создания процесса от размера его образа в памяти и вариации этого времени на несколько порядков. Об этом уже говорилось при рассмотрении функции
fork()
make
strip
memcpy()
fork()
На результаты наших оценок очень существенное влияние оказывают процессы кэширования памяти, что можно легко увидеть, экспериментируя с приложением, но затраты (число процессорных тактов) на выполнение
fork()
T = 3000000 + Р * 6000
где
Р
fork()
Теперь проведем столь же элементарный альтернативный тест ( файл p2-2.cc) по созданию потока. (В случае потока время гораздо проще измерять и с более высокой точностью, но мы для сравнимости результатов почти текстуально сохраним предыдущий пример с включением в результат операторов завершения дочернего объекта, ожидания результата и т.д.)
void* threadfunc(void* data) { pthread_exit(NULL); }