Потокобезопасность в C

предположите, что я пишу библиотеку в C. Далее, предположите, что эта библиотека используется от многопоточной среды. Как я делаю это ориентированным на многопотоковое исполнение? Более конкретный: Как я гарантирую, что определенные функции выполняются только одним потоком за один раз?

В напротив Java или C#, например, C не имеет никаких средств иметь дело с потоками/блокировками/и т.д., ни делает стандартную библиотеку C. Я знаю, что потоки поддержки операционных систем, но использование их API ограничило бы совместимость моей библиотеки очень. Какие возможности я имею, для хранения моей библиотеки максимально совместимой/портативной? (например, доверие OpenMP, или на Posix распараллеливает для хранения этого совместимым по крайней мере со всеми подобными Unix операционными системами?)

10
задан Matthew Murdoch 9 January 2010 в 10:27
поделиться

8 ответов

Вы можете создавать оболочки с помощью #ifdef. Это действительно лучшее, что ты можешь сделать. (Или вы можете использовать для этого стороннюю библиотеку).

Я покажу, как я это сделал, на примере Windows и Linux. Это на C ++, а не на C, но, опять же, это просто пример:

#ifdef WIN32
typedef HANDLE thread_t;
typedef unsigned ThreadEntryFunction;
#define thread __declspec(thread)

class Mutex : NoCopyAssign
{
public:
    Mutex() { InitializeCriticalSection(&mActual); }
    ~Mutex() { DeleteCriticalSection(&mActual); }
    void Lock() { EnterCriticalSection(&mActual); }
    void Unlock() { LeaveCriticalSection(&mActual); }
private:
    CRITICAL_SECTION mActual;
};

class ThreadEvent : NoCopyAssign
{
public:
    ThreadEvent() { Actual = CreateEvent(NULL, false, false, NULL); }
    ~ThreadEvent() { CloseHandle(Actual); }
    void Send() { SetEvent(Actual); }

    HANDLE Actual;
};
#else
typedef pthread_t thread_t;
typedef void *ThreadEntryFunction;
#define thread __thread
extern pthread_mutexattr_t MutexAttributeRecursive;

class Mutex : NoCopyAssign
{
public:
    Mutex() { pthread_mutex_init(&mActual, &MutexAttributeRecursive); }
    ~Mutex() { pthread_mutex_destroy(&mActual); }
    void Lock() { pthread_mutex_lock(&mActual); }
    void Unlock() { pthread_mutex_unlock(&mActual); }
private:
    pthread_mutex_t mActual;
};

class ThreadEvent : NoCopyAssign
{
public:
    ThreadEvent() { pthread_cond_init(&mActual, NULL); }
    ~ThreadEvent() { pthread_cond_destroy(&mActual); }

    void Send() { pthread_cond_signal(&mActual); }
private:
    pthread_cond_t mActual;
};

inline thread_t GetCurrentThread() { return pthread_self(); }
#endif

/* Allows for easy mutex locking */
class MutexLock : NoAssign
{
public:
    MutexLock(Mutex &m) : mMutex(m) { mMutex.Lock(); }
    ~MutexLock() { mMutex.Unlock(); }
private:
    Mutex &mMutex;
};
18
ответ дан 3 December 2019 в 14:06
поделиться
[

] Запишите свой собственный замок. [

] [

] Так как вы нацелены на ПК, вы имеете дело с архитектурой x86, которая вроде бы поставляет всю необходимую поддержку многопоточности. Просмотрите свой код и определите все функции, которые имеют общие ресурсы. Дайте каждому общему ресурсу 32-битный счетчик. Затем, используя заблокированные операции, которые реализуются процессорами, отследите, сколько потоков используют каждый разделяемый ресурс и заставьте любой поток, который хочет использовать разделяемый ресурс, подождать, пока ресурс не будет выпущен.[

] [

]Вот действительно хороший пост в блоге о заблокированных операциях: [] Использование взаимосвязанных инструкций на C/C++... [][

] [

]Автор фокусируется в основном на использовании обёртки Win32 Interlocked, но практически каждая операционная система имеет свои собственные обёртки для операций с блокировкой, и вы всегда можете записать сборку (каждая из этих операций - только одна инструкция)[

].
3
ответ дан 3 December 2019 в 14:06
поделиться

Заблуждение, что библиотека pthreads не работает в Windows. Посетите sourceforge.net . Я бы порекомендовал pthreads, потому что он кроссплатформенный и его мьютексы намного быстрее, чем, например, встроенные мьютексы Windows.

3
ответ дан 3 December 2019 в 14:06
поделиться

Также следует избегать статических и глобальных переменных, которые можно модифицировать, избегая кода синхронизации по всему модулю

.
2
ответ дан 3 December 2019 в 14:06
поделиться

Использование Posix нитей кажется мне хорошей идеей (но я не эксперт). В частности, в Posix есть хорошие примитивы для обеспечения взаимного исключения.

Если бы пришлось создавать библиотеку без зависимостей, то пришлось бы самостоятельно реализовывать алгоритмы взаимного исключения, что является плохой идеей.

0
ответ дан 3 December 2019 в 14:06
поделиться

У вас есть два основных варианта:

1) Вы указываете , в какой многопоточной среде ваша библиотека является потокобезопасной, и используете функции синхронизации этой среды. .

2) Вы указываете, что ваша библиотека не потокобезопасна. Если ваш вызывающий объект хочет использовать его в многопоточной среде, то он обязан сделать его потокобезопасным, используя при необходимости внешнюю синхронизацию для сериализации всех вызовов вашей библиотеки. Если ваша библиотека использует дескрипторы и не нуждается в каком-либо глобальном состоянии, это может, например, означать, что если у них есть дескриптор, который они используют только в одном потоке, то им не нужна никакая синхронизация для этого дескриптора, потому что он автоматически сериализуется.

Очевидно, что вы можете применить многопакетный подход к (1) и использовать константы времени компиляции для поддержки всех известных вам сред.

Вы также можете использовать архитектуру обратного вызова, зависимость времени компоновки или макросы, чтобы ваш вызывающий пользователь мог сказать вам, как синхронизировать. Это своего рода смесь (1) и (2).

Но не существует такой вещи, как стандартная многопоточная среда, поэтому практически невозможно написать автономный код, который является потокобезопасным везде, если он полностью не имеет состояния (то есть все функции не имеют побочных эффектов) . Даже в этом случае вам придется толковать «побочный эффект» свободно, поскольку, конечно, стандарт C не определяет, какие библиотечные функции являются потокобезопасными. Это немного похоже на вопрос, как написать код C, который может выполняться в обработчике аппаратных прерываний. «Что такое прерывание?», - вы вполне можете спросить, «и какие вещи, которые я мог бы сделать в C, недопустимы в нем?». Единственные ответы зависят от ОС.

4
ответ дан 3 December 2019 в 14:06
поделиться

Вам нужно будет использовать библиотеку потоков вашей ОС. В Posix это обычно pthreads , и вам понадобится pthread_mutex_lock .

В Windows есть собственная библиотека потоков, и вы захотите посмотреть либо критические разделы , либо CreateMutex . Критические секции более оптимизированы, но ограничены одним процессом, и вы не можете использовать их в WaitForMultipleObjects .

5
ответ дан 3 December 2019 в 14:06
поделиться

Если ваша цель - совместимость с unix-подобными операционными системами, я бы использовал многопоточность POSIX.

При этом, если вы хотите также поддерживать Windows, вам понадобится два пути кода для этого - pthreads в unix и потоки Windows в Windows. Достаточно легко создать свою собственную «библиотеку потоков», чтобы обернуть их.

Есть немало таких, которые делают это (например, OpenThreads ), но большинство из них, которые я использовал, - это C ++, а не C.

1
ответ дан 3 December 2019 в 14:06
поделиться
Другие вопросы по тегам:

Похожие вопросы: