Многопоточность в C/C++ с использованием pthreads

21 июня 2017

Необходимость написания многопоточных приложений возникает весьма и весьма часто. В мире C/C++ эту задачу можно решать по-разному. Так в стандарте C++11 и старше есть std::thread и другие примитивы. Также поддержка трэдов есть в стандарте C11 (threads.h). Однако если эти стандарты вам по каким-то причинам не доступны, или ваше приложение не планируется в обозримом будущем портировать под Windows, можно воспользоваться старой-доброй библиотекой pthreads.

Если для сборки проекта вы используете CMake, pthreads подключается проще простого:

find_library(PTHREAD_LIBRARY pthread)
# ...
target_link_libraries(main ${PTHREAD_LIBRARY})

Новый трэд создается как-то так:

#include <pthread.h>

static void* _workerThreadProc(void* rawArg) {
    /* ... достаем аргументы по указателю rawArg ... */

    /* No other thread is going to join() this one */
    pthread_detach(pthread_self());

    /* ... тут всякая полезная нагрузка ... */

    pthread_exit(nullptr);
}

// ...

pthread_t thr;
if(pthread_create(&thr, nullptr, _workerThreadProc, (void*)arg) != 0)
    throw std::runtime_error("pthread_create() failed");

Если планируется дожидаться завершения трэда, нужно закомментировать строчку про pthread_detach и использовать pthread_join. Подробности вы найдете в соответствующих man-страницах.

Отдельная тема — это взаимодействие трэдов с сигналами. Например, при написании сетевых приложений, часто требуется блокировать SIGPIPE. Делается это следующим образом:

void _ignoreSigpipe() {
    sigset_t msk;
    sigemptyset(&msk);
    sigaddset(&msk, SIGPIPE);
    if(pthread_sigmask(SIG_BLOCK, &msk, nullptr) != 0)
        throw std::runtime_error("pthread_sigmask() failed");
}

Трэды сами по себе не очень полезны, если нет каких-то примитивов синхронизации. Самый простой примитив, предлагаемый pthreads — это мьютекс:

pthread_mutex_t my_lock;

// ...

pthread_mutex_lock(&my_lock);
// do something
pthread_mutex_unlock(&my_lock);

Также есть чуть более интересный примитив — rwlock:

pthread_rwlock_t my_rwlock;

// инициализация
if(pthread_rwlock_init(&my_rwlock, NULL) != 0)
    throw std::runtime_error("pthread_rwlock_init() failed");

// лок на чтение
pthread_rwlock_rdlock(&my_rwlock);

// лок на запись
pthread_rwlock_wrlock(&my_rwlock);

// анлок
pthread_rwlock_unlock(&my_rwlock)

// деинициализация
pthread_rwlock_destroy(&my_rwlock);

Атомарных переменных, насколько мне известно, pthread не предлагает. Однако при сильной необходимости ничто не мешает использовать атомарные переменные из C++11.

В общем-то, описанного выше в 90% случаев будет вполне достаточно для написания многопоточных приложений при помощи pthreads. Более полный пример можно найти в этом репозитории на GitHub. Во всяком случае, до коммита a46c9835 pthreads там еще использовались. В будущем ситуация может и поменяться.

А как вы пишите многопоточные приложения на C/C++?

Дополнение: Также вас могут заинтересовать посты Написание многопоточных приложений на C++11 и старше и Пример простейшей многопоточной программы на WinAPI.

Метки: , .


Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.