Правильная реализация 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.