Какие примитивы синхронизации я могу использовать с clone (2) (C / C ++)? - PullRequest
3 голосов
/ 20 января 2012

Какие примитивы синхронизации C ++ можно использовать при использовании потоков Linux clone (2)? Я специально не могу использовать pthreads, потому что я создаю общую библиотеку, которая заменяет многие вызовы функций pthreads различными определениями, ноМне нужен какой-то мьютекс.

РЕДАКТИРОВАТЬ: Я мог бы говорить слишком рано, я посмотрел на документы pthread, и они используют futex (2) для реализации этих примитивов.Я полагаю, что так бы и поступил?

Ответы [ 3 ]

7 голосов
/ 20 января 2012

Вы можете использовать futex http://en.wikipedia.org/wiki/Futex

Вот простой mutex и cond var на основе futex http://locklessinc.com/articles/mutex_cv_futex/

2 голосов
/ 21 января 2012

В зависимости от ваших требований к инструменту синхронизации вы также можете использовать атомарные операции. Например, * gcc __sync_lock_test_and_set можно легко использовать для спин-блокировки. Такие блокировки пользовательского пространства, которые избегают системных вызовов, во многих случаях могут быть более эффективными, чем решения на основе ядра.

Редактировать: Это тот случай, если у вас есть всего несколько инструкций для защиты. Тогда вероятность того, что нить, которая взяла спин-блокировку, прерывается при удержании блокировки, очень мала. Если это произойдет, тогда время ожидания будет составлять несколько циклов планирования, что в современных системах должно быть только запретом для систем реального времени. Для среднего времени ожидания это должно быть незначительным.

1 голос
/ 14 марта 2012

Правильная реализация Mutex на основе futex описана в статье Ульриха Дреппера «Futexes is tricky». Ссылка, предоставленная osgx в его комментарии, устарела. Текущая версия здесь:

http://people.redhat.com/drepper/futex.pdf

Включает в себя не только код, но и очень подробное объяснение того, почему он правильный. Код из бумаги:

class mutex
{
 public:
 mutex () : val (0) { }
 void lock () {
   int c;
   if ((c = cmpxchg (val, 0, 1)) != 0)
     do {
       if (c == 2 || cmpxchg (val, 1, 2) != 0)
         futex_wait (&val, 2);
     } while ((c = cmpxchg (val, 0, 2)) != 0);
 }
 void unlock () {
   if (atomic_dec (val) != 1) {
     val = 0;
     futex_wake (&val, 1);
   }
 }
 private:
   int val;
};

cmpxchg () и atomic_dec () могут быть реализованы через __sync_val_compare_and_swap () и __sync_fetch_and_add () соответственно, которые не являются стандартными C, но также поддерживаются GCC и AFAIK CLANG.

Обратите внимание, что futex () специфичен для Linux. FreeBSD имеет эмуляцию AFAIK, но я не знаю, можете ли вы получить к ней доступ изначально. Если вы хотите использовать другие платформы, кроме Linux, вы не можете использовать futex (). Но поскольку вы используете clone () для Linux, вам, вероятно, все равно.

О вашей идее написать замену pthreads в целом:

Забудь об этом.

Я пошел по этому пути сам, потому что хотел, чтобы какое-то поведение предлагалось clone (), но не pthread_create (). Через некоторое время я сдался. glibc основан на предположении, что вы используете pthreads. Даже (или особенно), если вы не связываетесь с -lpthread, glibc включает поведение, специфичное для pthread. То, что заставило меня отказаться от clone (), было ошибочным. В многопоточном приложении вы должны указать локальный поток, если вы не хотите синхронизировать каждый вызов libc с глобальным мьютексом. Вы делаете это путем реализации __errno_location (). К сожалению, в некоторых местах glibc имеет свой собственный __errno_location и не будет использовать вашу замену.

Другими словами: если вы хотите использовать свою собственную библиотеку потоков, вы не можете использовать glibc, по крайней мере без чтения исходного кода для каждой функции, которую вы используете, и готовности заменить ее в случае необходимости. И если вы думаете об использовании uClibc вместо этого, я должен вас разочаровать. Они скопировали части своей реализации из glibc, и, по крайней мере, в более старых версиях возникла вышеуказанная проблема __errno_location.

Моим решением было отказаться от боевых действий. Теперь я использую pthread_create () для создания своих потоков (конечно, с помощью красивой оболочки C ++). Хотя я еще не пробовал, системный вызов unshare (2) должен позволить мне изменить те аспекты потока, которые мне хотелось бы установить через clone (), которые pthread_create () не поддерживает. Если это не сработает, я возьму исходный код glibc для pthread_create () и взломаю мои параметры в вызове clone (), оставив остальные идентичные, чтобы не нарушать совместимость.

Что касается примитивов синхронизации: использование pthread_create () НЕ заставляет вас использовать pthread_mutex и company. Поскольку я пишу виртуальную машину, в которой каждому объекту назначен мьютекс, я не хочу использовать служебную память pthread_mutex_t. Вместо этого я использую класс Mutex Дреппера из статьи выше. Вы можете свободно смешивать и сочетать примитивы pthread и свои собственные. По возможности, вы должны использовать версии pthread, потому что они хорошо протестированы. Но для особых случаев (таких как чрезвычайно легкий мьютекс) можно создавать собственные примитивы на основе futex.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...