Синхронизация потоков в C++/CLI с использованием только атомарных операций

В настоящее время я пытаюсь добиться синхронизации потоков в С++ .net, используя только атомарные операции, используя класс System::Threading::Interlocked в .net. Я не очень разбираюсь в многопоточности и пытаюсь использовать синхронизацию потоков без блокировки. В настоящее время я создал класс с именем settings, который содержит статические переменные, которые мне нужно разделить между потоками. Затем я создал в нем две статические функции: одну для установки статических данных-членов и одну для их чтения. В настоящее время так выглядит одна из моих статических функций синхронизации. Я понимаю, что если в эту функцию одновременно войдут более двух потоков, они могут навсегда застрять в цикле while, но эта функция будет нужна только двум потокам: потоку с графическим интерфейсом и основному потоку, который будет читать настройки и отправлять работу рабочим потокам.


//object is handle to instance of settings class that also contains non-static
//members that will contain each threads copy of the data.
void Settings::SetStaticVariables(Settings ^object)
{
    int returnvalue;

    //canchange variable is a static integer of the class "Settings"
    returnvalue = Threading::Interlocked::Increment(Settings::canchange);
    if(returnvalue > 1)
    {
        while(Settings::canchange > 1)
        {
            //perhaps eventually I will find an alternative to telling the thread
            //to sleep for a defined amount of time, maybe when I learn how to use events
            //for now this will do, speed is not very important for this data sync as 
            //it does not occure often
            Threading::Thread::Sleep(50);
        }
    }
    //data synchronization of static members here

    //decrement allowing waiting threads to exit while loop
    Threading::Interlocked::Decrement(Settings::canchange);
};

Мой вопрос в том, видите ли вы что-то ошибочное, что не даст мне того, что я ожидаю, или вся эта идея синхронизации ошибочна?


person contrapsych    schedule 03.02.2011    source источник
comment
Я не очень разбираюсь в многопоточности и пытаюсь использовать синхронизацию потоков без блокировки. Это, скорее всего, плохо кончится. Написать правильный многопоточный код сложно, а сделать это без блокировок еще сложнее. Пробовали ли вы использовать блокировку и профилировать ее, чтобы увидеть, является ли блокировка реальной проблемой производительности?   -  person James McNellis    schedule 03.02.2011
comment
Я не пробовал использовать какие-либо блокировки, но поскольку производительность для этого участка кода совсем не важна, то я вполне мог бы попробовать использовать блокировки. Если вы считаете, что это лучший путь, то я определенно мог бы использовать их. Я просто подумал, что было бы неплохо получить опыт синхронизации без блокировки, потому что я прочитал в статье о многопоточности, что лучше использовать блокировки как можно меньше. Кроме того, со временем я буду (пытаться) кодировать там, где скорость имеет значение, но пока, я полагаю, я мог бы познакомиться с блокировками.   -  person contrapsych    schedule 03.02.2011
comment
@Джеймс: +1. +10, если бы я мог. @Jake: Это правда, что избегание блокировок в целом является хорошей практикой, но гораздо более важным является правило об избегании синхронизации без блокировки, в частности, никогда не пишите код синхронизации без блокировки, если вы не являетесь экспертом. с опытом работы в несколько десятков лет, и у вас есть хорошая команда многопоточных программистов и продвинутых математиков, готовых проверить вашу работу.   -  person Stephen Cleary    schedule 03.02.2011
comment
Правильно: начните с изучения основ синхронизации потоков с помощью блокировок. Как только вы познакомитесь с основами, атомарность можно использовать для очень простого программирования без блокировок, хотя до тех пор, пока вы не освоитесь с написанием кода синхронизации, я бы не стал пытаться делать с ними что-то слишком сложное (я совсем не Я имею в виду это снисходительно; я написал более чем достаточно об ошибках, связанных с многопоточностью, и я твердо верю в необходимость придерживаться простых, прямолинейных, часто используемых шаблонов, о которых легко рассуждать.)   -  person James McNellis    schedule 03.02.2011
comment
Ну, я убежден, я пока буду использовать замки. Проведя еще несколько исследований, я обнаружил, что компилятор и процессор иногда переупорядочивают операции, которые могут привести к изменению данных в синхронизированном разделе после его выхода. Возможно, со временем я поэкспериментирую с другой программой исключительно с целью проверки синхронизации данных без блокировок, но пока воспользуюсь ими. Надеюсь, когда я поступлю в колледж и буду изучать информатику, у меня будет лучшее понимание параллелизма. Спасибо за совет, ребята.   -  person contrapsych    schedule 03.02.2011


Ответы (1)


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

Вы можете использовать InterlockedExchange вместо приращения, что устранит возможную взаимоблокировку с> = 2 потоками. Однако этот алгоритм вовсе не является программированием без блокировки, скорее вы реализуете свою собственную блокировку. Конечно, это означает, что вы столкнетесь со многими проблемами производительности и корректности, которые уже были решены блокирующими классами библиотеки (Monitor и друзья).

Вот как это будет выглядеть с Interlocked::Exchange.

// try to acquire lock
while (0 != Interlocked::Exchange(Settings::canchange, 1))
{
   Thread::Sleep(50);
}

MemoryBarrierAcquire();

// update shared variables

// flush cached writes
MemoryBarrierRelease();
// unlock
Settings::canchange = 0;
person Ben Voigt    schedule 03.02.2011
comment
Спасибо за совет, но я не буду пробовать без блокировок, пока не разберусь с их использованием и многопоточностью в целом лучше, чем сейчас. Кроме того, если бы вы могли, как именно многопоточность без блокировки отличается от того, что я сделал, реализовав свою собственную блокировку (думаю, я, вероятно, должен был понять это лучше, прежде чем даже задавать вопрос LOL). - person contrapsych; 04.02.2011
comment
Я знаю, что это было давно, но когда я вношу изменения в один поток, очищает ли memorybarrier кэш процессора, или мне нужно использовать изменчивые записи и чтения для поддержания когерентности кеша? Или это автоматически поддерживается процессором? - person contrapsych; 20.02.2011
comment
Барьер памяти не позволяет процессору переупорядочивать операции чтения и записи. Когда системная шина реализует протокол когерентности кеша, то на самом деле очистка кеша может не понадобиться - большая часть эффекта на самом деле приходится на механизм конвейера ЦП, включая неупорядоченное выполнение и спекулятивное выполнение. Volatile — это нечто совершенно другое, оно не позволяет компилятору переупорядочивать, объединять или генерировать инструкции для спекулятивного доступа к памяти. (За исключением VC++ 2008 и более поздних версий, volatile также добавляет барьер памяти.) - person Ben Voigt; 20.02.2011
comment
Так действительно ли я вообще могу использовать volatile? Я обнаружил, что это довольно бесполезно для многопоточности в большинстве случаев из нескольких статей и ответил на вопросы здесь и там. Должен ли я использовать его в случае, если я столкнусь с переменной, которая совместно используется и часто изменяется одним потоком, чтобы убедиться, что она не остается в регистре, или я должен полностью игнорировать это слово? (или мне просто задать новый вопрос вместо того, чтобы добавлять комментарии;) - person contrapsych; 21.02.2011
comment
@Jake: Вы, вероятно, не используете volatile. Это больше для кода режима ядра, взаимодействующего с устройствами, где двойное чтение одного и того же адреса может не только дать разные результаты, но и изменить состояние устройства. Также очень полезно во встроенных системах для переменных, совместно используемых с ISR. А в обработчиках сигналов в стиле unix, которые выполняются в одном потоке, аппаратное выполнение не по порядку не является фактором. Если вы не делаете ничего из этого, вы, вероятно, не будете его использовать. - person Ben Voigt; 21.02.2011
comment
@Jake: Однако есть один дополнительный эффект. Как и const, система типов C++ позволяет неявно добавлять volatile, но не удалять его. Таким образом, объявление переменной как volatile может позволить компилятору помочь вам проверить, передается ли она только в потокобезопасные API. - person Ben Voigt; 21.02.2011