Форум программистов «Весельчак У»
  *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

  • Рекомендуем проверить настройки временной зоны в вашем профиле (страница "Внешний вид форума", пункт "Часовой пояс:").
  • У нас больше нет рассылок. Если вам приходят письма от наших бывших рассылок mail.ru и subscribe.ru, то знайте, что это не мы рассылаем.
   Начало  
Наши сайты
Помощь Поиск Календарь Почта Войти Регистрация  
 
Страниц: [1] 2  Все   Вниз
  Печать  
Автор Тема: Атомарность операций над стандартными типами C++  (Прочитано 33749 раз)
0 Пользователей и 1 Гость смотрят эту тему.
RuNTiME
Помогающий

ru
Offline Offline
Пол: Мужской

« : 10-08-2011 17:45 » 

Есть переменная, тип int. Два потока пишут в эту переменную, а третий считывает значение этой переменной. Запись синхронизирована мьютексом, чтение не синхронизировано. Собственно вопрос в том, безопасно ли считывать значение этой переменной? Что будет, если потоки начнут одновременно читать и писать в эту переменную?
Записан

Любимая игрушка - debugger ...
Finch
Спокойный
Администратор

il
Offline Offline
Пол: Мужской
Пролетал мимо


« Ответ #1 : 10-08-2011 17:48 » 

Тут надо спросить, а сколько процессоров есть? Если один, то ничего не будет. Да и со множеством процессоров я так думаю ничего не будет. Операция чтения из памяти превращается в ассемблерную команду MOV AX, [SOME_ADRESS] Для потока гарантировано, что регистры не будут изменять свои значения. Но если у тебя например читаюший поток несколько раз использует одну и туже переменную для вычисления какой либо формулы, то тут могут быть и приключения. Так как во время вычисления, значение будет считываться несколько раз.
« Последнее редактирование: 10-08-2011 17:57 от Finch » Записан

Не будите спашяго дракона.
             Джаффар (Коша)
RuNTiME
Помогающий

ru
Offline Offline
Пол: Мужской

« Ответ #2 : 10-08-2011 17:52 » 

Хотелось бы рассмотреть общий случай, пусть будет произвольное количество процессоров/ядер  Улыбаюсь
Записан

Любимая игрушка - debugger ...
Finch
Спокойный
Администратор

il
Offline Offline
Пол: Мужской
Пролетал мимо


« Ответ #3 : 10-08-2011 17:55 » 

Дописал для обшего случая Улыбаюсь
Записан

Не будите спашяго дракона.
             Джаффар (Коша)
RuNTiME
Помогающий

ru
Offline Offline
Пол: Мужской

« Ответ #4 : 10-08-2011 18:01 » 

Хмм, в общем я так понимаю, если размер переменной не превышает размер регистра процессора, то операция чтения будет атомарной. Но если к примеру попытаться провернуть такой финт с 64 битной переменной на 32 разрядном процессоре, словим глюк.
Записан

Любимая игрушка - debugger ...
Finch
Спокойный
Администратор

il
Offline Offline
Пол: Мужской
Пролетал мимо


« Ответ #5 : 10-08-2011 18:06 » 

Не обязательно. На x86 процессорах младшие биты идут всегда первыми. А старшие биты относительно редко будут меняться. Хотя это все дело случая. И отлавливать внезапные ошибки будет сложновато.

Кстати, тип int по стандарту всегда равен разрядности системы. Хотя я встречал для 64 разрядных систем, что тип int был 32 разрядный.
« Последнее редактирование: 10-08-2011 18:09 от Finch » Записан

Не будите спашяго дракона.
             Джаффар (Коша)
Антон (LogRus)
Глобальный модератор

ru
Offline Offline
Пол: Мужской
Внимание! Люблю сахар в кубиках!


WWW
« Ответ #6 : 11-08-2011 04:14 » 

тут не важно 32 или 64, процессор в любом случае не оперирует столь маленькими кусками при чтении памяти, он оперирует кэш линиями, оба этих типа не могут быть разделены на 2 кэш линии, если ты конечно не делаешь грязный хак ввиде копирования char * в int через reintepret_cast
так же нужно помнить, про volitile - без него даже защищённая мьютексом переменная может давать спецэффекты
а кроме всего прочего вопрос атомарно ли чтение или запись не сильно полезен в большенстве случаев, не так уж часто нужно просто изменить переменную или прочитать, обычно перед или после с переменной, что-то делают и нужно смотреть, что именно возможно тут нужно сделать копию переменной или же защищать мьютексом все вычисления зависящие от переменной, защищать непосредственно запись часто безсмысленно (когда мы говорим о таком объекте как int)
Записан

Странно всё это....
darkelf
Молодой специалист

ua
Offline Offline

« Ответ #7 : 11-08-2011 06:28 » 

Кроме всего прочего ещё есть функции, специфичные для каждой платформы, для работы с atomic-переменными, например семейство Interlocked*() в win или atomic_*() в unix-подобных.
« Последнее редактирование: 11-08-2011 06:33 от darkelf » Записан
RuNTiME
Помогающий

ru
Offline Offline
Пол: Мужской

« Ответ #8 : 11-08-2011 06:50 » 

Антон (LogRus), Если переходить на конкретный случай, то эта переменная отражает состояние в классте потока. Что - то навроде "Новый", "Запущен", "Приостановлен", "Остановлен". И защищать на запись обязательно т.к. изменять её могут множество потоков, которым требуется управлять данным классом потока. И да, безусловно защищены все вычисления и манипуляции приводящие к изменению этой переменной. Под вопросом только считывание этого состояния без блокирования переменной. Насчет volatile, которая отключает оптимизацию переменной, думаю что побочных эффектов не будет, если защищен весь код, который изменяет эту переменную.

darkelf, Атомную переменную можно реализовать при помощи одного мьютекса и обычной целочисленной переменной. По этому не вижу особого смысла использовать платформо-зависимые функции для такого простого объекта.

PS: Посмотрел реализацию потоков в библиотеке wxWidgets там переменную состояния потока на чтение не защищают...
Записан

Любимая игрушка - debugger ...
darkelf
Молодой специалист

ua
Offline Offline

« Ответ #9 : 11-08-2011 07:33 » 

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

ru
Offline Offline
Пол: Мужской

« Ответ #10 : 11-08-2011 07:42 » 

darkelf, Возможно со своей реализацией накладные расходы будут выше, но не намного. Все эти системные функции так же обращаются к ядру и могут заблокировать поток на время изменения значения переменной. Иначе как им синхронизировать изменения переменной и недопустить состояния гонки?  Ага

Вот выдержка из описания к переменной atomic_t:
These functions manipulate variables of type atomic_t is SMP and interrupt safe ways. These variables can be used to hold spin locks or SMP-safe reference counters. These functions guarantee that the operation that they represent is performed correctly. If necessary, hardware bus locking is performed to protect the operation. Usually, the CPU has some sort of atomic instructions that allow these operations to be performed quickly and safely.
« Последнее редактирование: 11-08-2011 07:49 от RuNTiME » Записан

Любимая игрушка - debugger ...
darkelf
Молодой специалист

ua
Offline Offline

« Ответ #11 : 11-08-2011 08:21 » 

RuNTiME, нет, не обращаются, зачастую это макрос, с каким-нибудь cmpxchg внутри, ну или чем-то чуть более сложным, но без вызова ядра. Вы как-раз привели цитату про "CPU has some sort of atomic instructions".
« Последнее редактирование: 12-08-2011 07:31 от darkelf » Записан
Антон (LogRus)
Глобальный модератор

ru
Offline Offline
Пол: Мужской
Внимание! Люблю сахар в кубиках!


WWW
« Ответ #12 : 11-08-2011 08:33 » 

Антон (LogRus), Если переходить на конкретный случай, то эта переменная отражает состояние в классте потока. Что - то навроде "Новый", "Запущен", "Приостановлен", "Остановлен". И защищать на запись обязательно т.к. изменять её могут множество потоков, которым требуется управлять данным классом потока. И да, безусловно защищены все вычисления и манипуляции приводящие к изменению этой переменной. Под вопросом только считывание этого состояния без блокирования переменной. Насчет volatile, которая отключает оптимизацию переменной, думаю что побочных эффектов не будет, если защищен весь код, который изменяет эту переменную.
"думаю не будет" и "не будет" это разные вещи, если ты эту переменную читаешь в каком нибуть цикле рабочего потока и в зависимости от значения выходишь из потока или отправляешь его вспячку или еще чего, то ты имеешь все шансы огрести по полной программе, компилятор не сильно в курсе, что ты собираешься её менять, где-то снаружи и имеет полное право нарисовать код который зачитает её 1 раз перед началом цикла, выполнит пару проверок и далее забьёт на неё здоровенный болт и ты можешь хоть как её писать и переписывать в паралельных потоках, сгеренированному коду это будет не особо интерестно

чтоже касается примитивов синхронизации, то лично я предпочитаю использовать spin мьютекс из tbb (и прочие прелести), ребята из intel знают, как правильно строить блокировки Улыбаюсь
Записан

Странно всё это....
RuNTiME
Помогающий

ru
Offline Offline
Пол: Мужской

« Ответ #13 : 11-08-2011 08:46 » 

darkelf, А так же привели пару фраз: These variables can be used to hold spin locks or SMP-safe reference counters. и If necessary, hardware bus locking is performed to protect the operation. Так что аж 2 вида блокировок используется. Спинлоки - да не требуют вызова кода ядра т.к. представляют собой обычный замкнутый цикл. А вот hardware locking думаешь кто осуществляет?  Улыбаюсь

Антон (LogRus), Я наверное не очень хорошо описал то что происходит. В коде нет глобальной переменной которую можно менять по всей программе. Переменная закрыта в private секции класса Thread и изменяется соответственно только функциями этого класса. Все функции этого класса, которые изменяют значение этой переменной в самом начале получают блокировку мьютекса (получается своеобразная критическая секция).

Вот так выглядит суть:
Код: (C++)
class Thread {
private:
    Mutex _lock;
    int _state;
public:
    void run();
    int getState() const { return _state; }
};

void Thread::run() {
    MutexLocker auto_lock(_lock); // в конструкторе MutexLocker устанавливает блокировку, в деструкторе освобождает.

    _state = 1; // изменяем переменную
}
Записан

Любимая игрушка - debugger ...
darkelf
Молодой специалист

ua
Offline Offline

« Ответ #14 : 11-08-2011 09:14 » 

darkelf, А так же привели пару фраз: These variables can be used to hold spin locks or SMP-safe reference counters. и If necessary, hardware bus locking is performed to protect the operation. Так что аж 2 вида блокировок используется. Спинлоки - да не требуют вызова кода ядра т.к. представляют собой обычный замкнутый цикл. А вот hardware locking думаешь кто осуществляет?  Улыбаюсь
это про то, что atomic_t используется для реализации spinlock-ов и smp reference counter-ов.  А hardware bus locking - это, если не ошибаюсь, в x86-ой архитектуре - префикс команды LOCK.
Записан
RuNTiME
Помогающий

ru
Offline Offline
Пол: Мужской

« Ответ #15 : 11-08-2011 09:23 » new

darkelf, Да ты прав. Только вот не представляю, каким образом он может обходится без блокировки потока.

PS: Почитал про команду LOCK. Теперь дошло. Окей буду использовать эти функции. Для реализации класса AtomicInt.
« Последнее редактирование: 11-08-2011 09:36 от RuNTiME » Записан

Любимая игрушка - debugger ...
darkelf
Молодой специалист

ua
Offline Offline

« Ответ #16 : 11-08-2011 09:39 » 

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

ru
Offline Offline
Пол: Мужской

« Ответ #17 : 11-08-2011 10:07 » 

простите, просмотрел тему мельком А черт его знает...,
 но пять копеек хочется вставить:
писать СВОЙ класс? а простите зачем, если функции атомарного доступа поддерживаются компилятором?
например, для microsoft-овского cl.exe это _InterlockedIncrement, _InterlockedExchangeAdd, _InterlockedCompareExchange и подобные ей для других - тоже должно быть, искать лень.

PS darkelf, про LOCK и кэш ты меня опередил))) да, все верно.
без префикса LOCK на чтение - там будут чудеса с очередностью записей через кэш.
« Последнее редактирование: 11-08-2011 10:19 от Ochkarik » Записан

RTFM уже хоть раз наконец!  RTFM :[ ну или хотя бы STFW...
RuNTiME
Помогающий

ru
Offline Offline
Пол: Мужской

« Ответ #18 : 11-08-2011 10:16 » 

Ochkarik, Ответ очень прост, код должен компилироваться на разных компиляторах, и даже если использовать условную компиляцию, написание кода превратится в ад. Проверок на условную компиляцию и так хватает за счет поддержки разных платформ... еще добавить условий для разных компиляторов, а возможно и версий компиляторов? Легче сразу застрелиться.....  Улыбаюсь

PS: В конце концов почему - то реализован класс atomic в той же STL...
« Последнее редактирование: 11-08-2011 10:18 от RuNTiME » Записан

Любимая игрушка - debugger ...
Ochkarik
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #19 : 11-08-2011 10:20 » 

RuNTiME, разных - это каких?
« Последнее редактирование: 11-08-2011 10:23 от Ochkarik » Записан

RTFM уже хоть раз наконец!  RTFM :[ ну или хотя бы STFW...
RuNTiME
Помогающий

ru
Offline Offline
Пол: Мужской

« Ответ #20 : 11-08-2011 10:23 » 

Ochkarik, А разве у нас есть только компилятор от microsoft? Ну например еще GCC... Или вдруг кому - то вздумается компилить библиотеку интеловским компилятором? А может Borland?
Записан

Любимая игрушка - debugger ...
Ochkarik
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #21 : 11-08-2011 10:39 » 

GCC interlocked http://gcc.gnu.org/onlinedocs/gcc-4.1.0/gcc/Atomic-Builtins.html
Microsoft http://msdn.microsoft.com/en-us/library/26td21ds(VS.71).aspx
Intel http://cache-www.intel.com/cd/00/00/34/76/347603_347603.pdf раздел Lock and Atomic Operation Related Intrinsics(почти microsoft)
все это переопределяется тупыми макросами.
борланд искать лень)
Записан

RTFM уже хоть раз наконец!  RTFM :[ ну или хотя бы STFW...
RuNTiME
Помогающий

ru
Offline Offline
Пол: Мужской

« Ответ #22 : 11-08-2011 10:49 » 

Ochkarik, За инфу спасибо, но:
1. Подобные махинации с макросами совсем не вписываются в парадигмы ООП
2. Написать класс гораздо проще чем перерывать мануалы всех существующих компиляторов
3. Оборачивая классами различные платформо-зависимые функции, получаешь легко переносимый и читабельный код

PS: Подобные директивы компилятора лучше всего использовать если точно знаешь, что код будет собираться именно этим компилятором и именно на этой платформе.
Записан

Любимая игрушка - debugger ...
Вад
Модератор

ru
Offline Offline
Пол: Мужской

« Ответ #23 : 11-08-2011 10:52 » 

Ochkarik, Ответ очень прост, код должен компилироваться на разных компиляторах, и даже если использовать условную компиляцию, написание кода превратится в ад.
Ну почему сразу в ад? Можно ведь сделать обёртку и компилировать её по-разному для разных платформ. Все условия препроцессора тогда будут в одном месте (или и вовсе можно иметь один заголовок на всё, и к нему - сколько нужно модулей, реализующих под каждую платформу по-своему, компилирующихся каждый под свою платформу)
Записан
Ochkarik
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #24 : 11-08-2011 11:01 » 

это не махинации) а ООП для этого использовать это... подземный ход через чердак)
и кстати... а кто такой  MutexLocker?
Записан

RTFM уже хоть раз наконец!  RTFM :[ ну или хотя бы STFW...
RuNTiME
Помогающий

ru
Offline Offline
Пол: Мужской

« Ответ #25 : 11-08-2011 11:04 » 

Вад,

Цитата
Можно ведь сделать обёртку и компилировать её по-разному для разных платформ.
Так и делаю Улыбаюсь

Цитата
Все условия препроцессора тогда будут в одном месте...
Все условия препроцессора обычно не получается сделать в одном месте, т.к. например когда пишешь под Linux системы в зависимости от версий системных библиотек или наличия/отсутствия каких либо библиотек программой может использоваться разный набор функций. Или даже другие алгоритмы для эмуляции отсутствующих функций. И приходится плодить кучу условных операторов препроцессора даже в условиях одной платформы. И примешивать еще и проверки на разные компиляторы мне совсем не хочется...

Да и вообще, что мы заговорили о директивах компилятора? Чем оно лучше обертывания обычных системных вызовов?
Записан

Любимая игрушка - debugger ...
Ochkarik
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #26 : 11-08-2011 11:08 » 

Вад,
Цитата
Можно ведь сделать обёртку и компилировать её по-разному для разных платформ.
Так и делаю Улыбаюсь
так  и все так делают)
Цитата
Да и вообще, что мы заговорили о директивах компилятора? Чем оно лучше обертывания обычных системных вызовов?
эх... только тем, что ресурсы на это не тратятся.
Записан

RTFM уже хоть раз наконец!  RTFM :[ ну или хотя бы STFW...
RuNTiME
Помогающий

ru
Offline Offline
Пол: Мужской

« Ответ #27 : 11-08-2011 11:18 » 

это не махинации) а ООП для этого использовать это... подземный ход через чердак)
и кстати... а кто такой  MutexLocker?

Почему подземный ход через чердак? Если библиотека полностью ООП, какой смысл в ней использовать такие хаки?

MutexLocker это очень простой класс, который позволяет заблокировать мьютекс при входе в функцию или блок, а потом
автоматически разблокировать его.
Код: (C++)
// И позволяет избежать таких вот косяков:
// как видно мьютекс останется залоченым если условие выполниться
{
    mutex.lock()
    if(<условие>) {
        // какая - то ошибка
        return -1;
    }
    mutex.unlock()
}

// С MutexLocker все будет работать отлично:
{
    MutexLocker __lock(lock);
    if(<условие>) {
        // какая - то ошибка
        return -1;
    }
}
// в любом случае, хоть через return хоть просто выйдя за пределы блока
// мьютекс будет автоматически разблокирован в деструкторе класса MutexLocker

Цитата
эх... только тем, что ресурсы на это не тратятся.
Очень спорный вопрос....
Записан

Любимая игрушка - debugger ...
Ochkarik
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #28 : 11-08-2011 12:01 » 

да нет, я просто офигиваю маленько... в процессоре есть аппаратная команда, специально для этого предназначенная, максимально эффективная, но использовать ее - религия ООП не позволяет...

а код какой то... непонятный) особенно последний блок - ума не приложу что он должен делать)
Записан

RTFM уже хоть раз наконец!  RTFM :[ ну или хотя бы STFW...
RuNTiME
Помогающий

ru
Offline Offline
Пол: Мужской

« Ответ #29 : 11-08-2011 12:10 » 

Ochkarik,
Цитата
в процессоре есть аппаратная команда...
Да и выше уже обсуждали ряд макросов atomic_* которые как раз и используют эту самую аппаратную команду...

Цитата
а код какой то... непонятный) особенно последний блок - ума не приложу что он должен делать)
А тут сложного ничего нет, создаём экземпляр класса MutexLocker при этом естественно автоматически вызывается его конструктор,
который вызывает mutex.lock() переданного ему мьютекса. Далее когда происходит выход из блока экземпляр класса MutexLocker автоматически
разрушается при этом вызывается его деструктор, который в свою очередь вызывает mutex.unlock().
Записан

Любимая игрушка - debugger ...
Страниц: [1] 2  Все   Вверх
  Печать  
 

Powered by SMF 1.1.21 | SMF © 2015, Simple Machines