Безопасно ли смешивать функции потоковой обработки потоков pthread.h и С++ 11?
Можно ли создать поток с помощью pthread_create
и безопасно использовать std::mutex
?
Я бы подумал, что если std::mutex
реализован как pthread_mutex_t
, тогда это будет хорошо, но я не вижу этого документально зарегистрированного
Например:
#include <pthread.h>
#include <mutex>
namespace {
std::mutex global_lock;
}
void* thread_func(void* vp) {
// std::mutex used in thread spawned with pthread_create
std::lock_guard<std::mutex> guard(global_lock);
// critical section
return nullptr;
}
int main() {
pthread_t tid;
pthread_create(&tid, nullptr, thread_func, nullptr);
pthread_join(tid, NULL);
}
Кстати, я запускаю Debian Wheezy.
Ответы
Ответ 1
Вы можете на моей машине (Debian тоже). Но я не уверен, буду ли я называть это безопасным.
Если вы посмотрите на соответствующий файл, /usr/include/c++/4.7/i486-linux-gnu/bits/gthr-default.h
в моем случае, вы увидите, что будет отображение 1:1 на pthreads api. <mutex>
использует __gthread_mutex_lock
для блокировки, которая точно определена там pthread_mutex_lock
. Или вы увидите, что std::thread
объявляет typedef __gthread_t native_handle_type;
Я не знаю, есть ли документированный способ проверить, используются ли pthreads. Но gthr-default.h
определяет _GLIBCXX_GCC_GTHR_POSIX_H
как включить guard, и я думаю, что до тех пор, пока этот макрос определен, вы можете предположить, что вы можете их смешивать.
Изменить: учитывая подсказку @Wakely, я бы написал:
template <typename T>
using strip = typename std::remove_pointer<typename std::decay<T>::type>::type;
static_assert(std::is_same<strip<std::thread::native_handle_type>, pthread_t>::value,
"libstdc++ doesn't use pthread_t");
Ответ 2
В какой-либо спецификации нет никакой гарантии, что она будет работать, но, вероятно, любая реализация С++ в ОС, использующая pthreads в качестве единственной реальной библиотеки потоков, будет использовать pthreads под потоками С++, поэтому она, скорее всего, будет работать.
Вероятно, вы столкнетесь с проблемами, если позже попытаетесь перенести код на другую платформу, которая использует нечто, отличное от pthreads, даже если эта платформа поддерживает также pthreads (например, windows).
Возникают вопросы, зачем это беспокоить и рисковать? Если вы используете С++ 11 std::mutex
, почему бы не использовать std::thread
?
Ответ 3
Оба std::thread
и std::mutex
имеют метод native_handle
, который позволяет вам копать до реализации платформы данного объекта. Это говорит мне, что стандартная библиотека потоковой обработки спроектирована так, чтобы хорошо сочетаться с реализацией платформы.
В качестве сторонних std::thread
и std::mutex
используются разные объекты, которые делают разные вещи, а именно: управлять потоками и обеспечивать синхронизацию поперечных потоков. В конце ядро делает тяжелый подъем.
Итак, если вас не беспокоит переносимость, я не понимаю, почему это должно быть проблемой.
В стороне, иногда вам может понадобиться реализация собственной платформы, чтобы предоставить вам более богатый набор функций, который позволяет платформа. Например, потоковая передача BSD позволяет использовать различные типы потоков, а некоторые библиотеки потоков позволяют вам устанавливать размер стека для вашего нового потока. API-интерфейсы потоков С++ представляют собой переносимый самый низкий общий знаменатель.
Ответ 4
Если ваш вопрос: могу ли я свободно переключаться между одним типом мьютекса и другим наугад? Тогда ответ будет отрицательным (если все нижние слои не используют одну и ту же реализацию, например pthread_mutex.)
Однако, если у вас есть разные группы ресурсов, которые вы хотите защитить, любая одна группа ресурсов может быть защищена с помощью одной реализации. На самом деле, иногда может быть лучше использовать семафор (то есть семафор полезен для определения одного замка для записи, много блокировок для чтения).
Итак, если у вас есть 4 группы ресурсов, которыми вы управляете, вы можете использовать:
- станд:: мьютекс,
- pthread_mutex,
- подталкивание:: мьютекс,
- семафоры.
То, что вы не можете сделать, это использовать boost:: mutex для доступа к данным, защищенным семафорами и наоборот, или std:: mutex для использования вещей, защищенных pthread_mutex.
В качестве простого примера, с точки зрения кода, это означает, что геттер и сеттер будут выполняться следующим образом:
void set(int new_value)
{
guard lock(my_mutex);
m_value = new_value;
}
int get() const
{
guard lock(my_mutex);
return m_value;
}
Две функции используют один и тот же мьютекс (здесь my_mutex
), и очевидно, что мьютекс имеет один тип.
Против этого вы не могли этого сделать:
void set(int new_value)
{
guard lock(this_mutex_here);
m_value = new_value;
}
int get() const
{
SafeGuard lock(that_mutex_there);
return m_value;
}
В этом втором примере вы используете два разных мьютекса, и это не будет работать так, как ожидалось, потому что блокировка в set()
не заблокирует блокировку в get()
и наоборот. Таким образом, в этом нет ничего безопасного (даже если один из охранников называется SafeGuard.)
Итак, это правило, если вы защитите m_value
с помощью мьютекса с именем my_mutex
, при каждом доступе к m_value
вы должны заблокировать мьютекс my_mytex
. Какая реализация, которую вы используете, не имеет значения, если вы согласны в этом.