Как и какие данные должны быть синхронизированы в многопоточном C ++ - PullRequest
0 голосов
/ 14 декабря 2011

Я создаю небольшое приложение, которое имеет поток рендеринга и несколько рабочих потоков для задач, которые могут быть выполнены рядом с рендерингом, например загрузка файлов на какой-либо сервер. Теперь в этих рабочих потоках я использую различные объекты для хранения информации обратной связи и делюсь ею с потоком рендеринга, чтобы прочитать их с целью вывода. Так что render = output, worker = input. Такими общими объектами являются int, float, bool, строка STL и список STL.

У меня это работало несколько месяцев, и все было в порядке, за исключением 2 случайных сбоев во время вывода, но теперь я узнал о синхронизации потоков. Я читаю int, bool и т. Д. Не требуется синхронизация, и я думаю, что это имеет смысл, но когда я смотрю на строку и список, я боюсь возможных сбоев, если два потока пытаются прочитать / записать объект одновременно. По сути, я ожидаю, что один поток изменяет размер строки, в то время как другой может использовать устаревший размер для циклического перебора его символов и последующего чтения из нераспределенной памяти. Сегодня вечером я хочу построить небольшой тестовый сценарий с 2-мя потоками, пишущими / читающими один и тот же объект в цикле, однако я надеялся получить некоторые идеи здесь.

Я читал о CriticalSection в Win32 и подумал, что стоит попробовать. И все же я не уверен, как лучше всего это реализовать. Если я поместил его в начале и в конце чтения / функции, я чувствую, что некоторое время было потрачено впустую. И если я обертываю EnterCriticalSection и LeaveCriticalSection в Set и Get Functions для каждого объекта, который я хочу синхронизировать между потоками, это большая администрация.

Я думаю, что мне нужно пролистать больше ссылок.


Хорошо, я все еще не знаю, как поступить. Я изучал ссылки, предоставленные StackedCrooked, но до сих пор не представляю, как это сделать.

Я сейчас скопировал / изменил это вместе и понятия не имею, как продолжить или что делать: у кого-то есть идеи?

class CSync
{
public:
    CSync()
    : m_isEnter(false)
    { InitializeCriticalSection(&m_CriticalSection); }
    ~CSync()
    { DeleteCriticalSection(&m_CriticalSection); }
    bool TryEnter()
    {
        m_isEnter = TryEnterCriticalSection(&m_CriticalSection)==0 ? false:true;
        return m_isEnter;
    }
    void Enter()
    {
        if(!m_isEnter)
        {
            EnterCriticalSection(&m_CriticalSection);
            m_isEnter=true;
        }
    }
    void Leave()
    {
        if(m_isEnter)
        {
            LeaveCriticalSection(&m_CriticalSection);
            m_isEnter=false;
        }
    }

private:
    CRITICAL_SECTION m_CriticalSection;
    bool m_isEnter;
};

/* not needed

class CLockGuard
{
public:
    CLockGuard(CSync& refSync) : m_refSync(refSync) { Lock(); }
    ~CLockGuard() { Unlock(); }

private:
    CSync& m_refSync;

    CLockGuard(const CLockGuard &refcSource);
    CLockGuard& operator=(const CLockGuard& refcSource);
    void Lock() { m_refSync.Enter(); }
    void Unlock() { m_refSync.Leave(); }
};*/

template<class T> class Wrap
{
public:
    Wrap(T* pp, const CSync& sync)
        : p(pp)
        , m_refSync(refSync)
    {}
    Call_proxy<T> operator->() { m_refSync.Enter(); return Call_proxy<T>(p); }
private:
    T* p;
    CSync& m_refSync;
};

template<class T> class Call_proxy
{
public:
    Call_proxy(T* pp, const CSync& sync)
        : p(pp)
        , m_refSync(refSync)
    {}
    ~Call_proxy() { m_refSync.Leave(); }
    T* operator->() { return p; }
private:
    T* p;
    CSync& m_refSync;
};


int main
{
    CSync sync;
    Wrap<string> safeVar(new string);
    // safeVar what now?
    return 0;
};

Ладно, сейчас я готовил небольшой тест, чтобы проверить, насколько удачны мои попытки, поэтому сначала я создал настройку для сбоя приложения, я поверил…

Но это не сбой !? Значит ли это, что теперь мне не нужна синхронизация? Что нужно программе для эффективного сбоя? И если он не падает, почему я вообще беспокоюсь. Кажется, я снова упускаю какой-то момент. Есть идеи?

string gl_str, str_test;

void thread1()
{
    while(true)
    {
        gl_str = "12345";
        str_test = gl_str;
    }
};

void thread2()
{
    while(true)
    {
        gl_str = "123456789";
        str_test = gl_str;
    }
};

CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)thread1, NULL, 0, NULL );
CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)thread2, NULL, 0, NULL );

Только что добавил больше материала, и теперь он вылетает при вызове clear (). Хорошо.

void thread1()
{
    while(true)
    {
        gl_str = "12345";
        str_test = gl_str;
        gl_str.clear();
        gl_int = 124;
    }
};

void thread2()
{
    while(true)
    {
        gl_str = "123456789";
        str_test = gl_str;
        gl_str.clear();
        if(gl_str.empty())
            gl_str = "aaaaaaaaaaaaa";
        gl_int = 244;
        if(gl_int==124)
            gl_str.clear();
    }
};

Ответы [ 3 ]

5 голосов
/ 14 декабря 2011

Правила просты: если объект может быть изменен в любом потоке, все обращения к нему требуют синхронизации. Тип объекта не имеет значения: даже bool или int требуют какой-либо внешней синхронизации (возможно, с помощью специальной, системно-зависимой функции, а не с помощью блокировки). Нет никаких исключений, по крайней мере, в C ++. (Если вы готовы использовать встроенный ассемблер и понимать значение ограждений и барьеров памяти, вы можете избежать блокировки.)

1 голос
/ 14 декабря 2011

Я читаю int, bool и т. Д. Не требуется синхронизация

Это не так:

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

Вы должны использовать мьютексы для защиты от состояний гонки.См. эту статью для быстрого ознакомления с библиотекой Boost Threading.

0 голосов
/ 14 декабря 2011

Во-первых, вам нужна защита даже для доступа к самым примитивным типам данных.Если у вас где-то есть int x, вы можете написать

x += 42;

... но это будет означать на самом низком уровне: прочитать старое значение x, вычислить новое значение, записать новое значениек переменной х.Если два потока сделают это примерно в одно и то же время, произойдут странные вещи.Вам нужен раздел блокировка / критический.

Я бы порекомендовал использовать C ++ 11 и связанные интерфейсы или, если это невозможно, соответствующие вещи из библиотеки boost :: thread.Если это тоже не вариант, критические разделы для Win32 и pthread_mutex_ * для Unix.

НЕТ, пока не начинайте писать многопоточные программы!

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

Когда функция-член изменяет переменную-член, инвариант может временно стать ложным, но этоХорошо, потому что функция-член будет следить за тем, чтобы все снова «сходилось» перед выходом.

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

...