Подтвердить что ты не робот

Как сделать поток приложений безопасным?

Я думал, что потокобезопасность, в частности, означает, что она должна удовлетворить потребность в нескольких потоках для доступа к тем же общим данным. Но, похоже, этого определения недостаточно.

Кто-нибудь может рассказать о том, что нужно сделать или позаботиться о том, чтобы сделать поток приложений безопасным.. Если возможно, дайте ответ на язык C/С++.

4b9b3361

Ответ 1

Существует несколько способов обеспечения безопасности потока.

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

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

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

Если все ваши функции являются потокобезопасными, а все ваши общие данные защищены должным образом, ваше приложение должно быть потокобезопасным.

Как сумасшедший Эдди сказал, это огромный вопрос. Я рекомендую читать потоки boost и использовать их соответственно.

Предостережение низкого уровня: компиляторы могут изменять порядок операторов, что может привести к потере безопасности потоков. С несколькими ядрами каждое ядро ​​имеет свой собственный кеш, и вам необходимо правильно синхронизировать кеши, чтобы обеспечить безопасность потоков. Кроме того, даже если компилятор не переупорядочивает операторы, аппаратное обеспечение может. Таким образом, полная гарантированная безопасность потоков сегодня не возможна. Вы можете получить 99,99% от того, как это происходит, и работать с поставщиками компиляторов и производителями процессора, чтобы устранить эту затяжную оговорку.

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

  • Определите любые данные, общие для потоков (если вы его пропустите, вы не можете его защитить)
  • создайте элемент boost::mutex m_mutex и используйте его всякий раз, когда вы пытаетесь получить доступ к данным совместного доступа (в идеале, общие данные являются приватными для класса, поэтому вы можете быть более уверенными в том, что вы его правильно защищаете).
  • очистить глобалы. Глобалы в любом случае плохи, и удачи, пытаясь сделать что-нибудь потокобезопасное с помощью глобальных переменных.
  • Остерегайтесь ключевого слова static. На самом деле это не потокобезопасно. Поэтому, если вы пытаетесь сделать синглтон, это не сработает правильно.
  • Остерегайтесь парадигмы с двойной проверкой. Большинство людей, которые его используют, ошибаются каким-то тонким образом, и склонны к поломке по поводу низкого уровня.

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

Ответ 2

Две вещи:

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

Например, если мы начинаем с:

// Globals
int x;
int y;

// Function that needs to be accessed by multiple threads
// currently relies on globals, and hence cannot work with
// multiple threads
int myFunc()
{
    return x+y;
}

После добавления в структуру состояния код становится:

typedef struct myState
{
   int x;
   int y;
} myState;

// Function that needs to be accessed by multiple threads
// now takes state struct
int myFunc(struct myState *state)
{
   return (state->x + state->y);
}

Теперь вы можете спросить, почему бы просто не передать x и y в качестве параметров. Причина в том, что этот пример является упрощением. В реальной жизни ваша государственная структура может иметь 20 полей и пропускать большинство этих параметров. 4-5 функций вниз становятся сложными. Вам лучше передать один параметр вместо многих.

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

Ответ 3

Если вы хотите сделать эксклюзивный доступ к методам класса, вы должны использовать блокировку этих функций.

Различные типы блокировок:

Использование atomic_flg_lck:

class SLock
{
public:
  void lock()
  {
    while (lck.test_and_set(std::memory_order_acquire));
  }

  void unlock()
  {
    lck.clear(std::memory_order_release);
  }

  SLock(){
    //lck = ATOMIC_FLAG_INIT;
    lck.clear();
  }
private:
  std::atomic_flag lck;// = ATOMIC_FLAG_INIT;
};

Использование atom:

class SLock
{
public:
  void lock()
  {
    while (lck.exchange(true));
  }

  void unlock()
  {
    lck = true;
  }

  SLock(){
    //lck = ATOMIC_FLAG_INIT;
    lck = false;
  }
private:
  std::atomic<bool> lck;
};

Использование мьютекса:

class SLock
{
public:
  void lock()
  {
    lck.lock();
  }

  void unlock()
  {
    lck.unlock();
  }

private:
  std::mutex lck;
};

Только для Windows:

class SLock
{
public:
  void lock()
  {
    EnterCriticalSection(&g_crit_sec);
  }

  void unlock()
  {
    LeaveCriticalSection(&g_crit_sec);
  }

  SLock(){
    InitializeCriticalSectionAndSpinCount(&g_crit_sec, 0x80000400);
  }

private:
  CRITICAL_SECTION g_crit_sec;
};

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

Как использовать эти критические разделы?

unique_ptr<SLock> raiilock(new SLock());

class Smartlock{
public:
  Smartlock(){ raiilock->lock(); }
  ~Smartlock(){ raiilock->unlock(); }
};

Использование идиомы raii. Конструктор блокирует критический раздел и деструктор, чтобы разблокировать его.

Пример

class MyClass {

   void syncronithedFunction(){
      Smartlock lock;
      //.....
   }

}

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

Я надеюсь, что вы найдете это полезным.

Спасибо!!

Ответ 4

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

Затем "решить" проблему производителя/потребителя, однако вы хотите, чтобы очереди не переполнялись или переполнялись. http://en.wikipedia.org/wiki/Producer-consumer_problem

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