Резьба безопасности в C - PullRequest
       10

Резьба безопасности в C

10 голосов
/ 08 января 2010

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

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

Ответы [ 9 ]

18 голосов
/ 08 января 2010

Вы можете создавать оболочки с #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;
};
5 голосов
/ 08 января 2010

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

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

4 голосов
/ 08 января 2010

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

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

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

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

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

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

3 голосов
/ 08 января 2010

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

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

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

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

3 голосов
/ 08 января 2010

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

2 голосов
/ 08 января 2010

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

1 голос
/ 20 сентября 2016

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

Вы не можете -> написать поточно-ориентированные или лучше повторно входящие функции. Если вы не хотите писать системные блокировки - очень плохая идея.

" В отличие от Java или C #, например, C не имеет средств для работы с потоками / блокировками / и т. Д. "

Это шутка, верно? Задолго до разработки Java и C # блокировки были изобретены и широко использовались в качестве объектов синхронизации ...

«Я знаю, что операционные системы поддерживают потоки, но использование их API сильно ограничит совместимость моей библиотеки.»

Дело в том, что такие библиотеки уже существуют - например, wxWidgets, которые предлагают переносимый wxThread ... (но это C ++)

В любом случае, есть два основных «вкуса» C: ANSI C и GNU C -> два разных мира ... выберите один или другой.

1 голос
/ 08 января 2010

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

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

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

0 голосов
/ 08 января 2010

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

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

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