Вот как может выглядеть в этой технике безопасный (с позиции возможной асинхронной отмены потока) захват мьютекса:
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()makestripmemcpy()fork()На результаты наших оценок очень существенное влияние оказывают процессы кэширования памяти, что можно легко увидеть, экспериментируя с приложением, но затраты (число процессорных тактов) на выполнение
fork()
T = 3000000 + Р * 6000где
Рfork()Теперь проведем столь же элементарный альтернативный тест ( файл p2-2.cc) по созданию потока. (В случае потока время гораздо проще измерять и с более высокой точностью, но мы для сравнимости результатов почти текстуально сохраним предыдущий пример с включением в результат операторов завершения дочернего объекта, ожидания результата и т.д.)
void* threadfunc(void* data) { pthread_exit(NULL); }