Russian Qt Forum
Апрель 19, 2024, 01:20 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: 1 2 [3]   Вниз
  Печать  
Автор Тема: memory_order  (Прочитано 18008 раз)
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3258


Просмотр профиля
« Ответ #30 : Май 29, 2019, 17:24 »

Думаю ничего "переставлять" процессор не умеет. Но он может одновременно исполнять "пачку" команд, при этом результат исполнения последующих может сформироваться раньше чем предыдущих, т.е. эффект тот же.
И в чем разница?)

Кароч, как правельно? Не сказал, вычитал в инете и измывается, гад
Правильно использовать синглтон Майерса. Начиная с С++11 стандарт гарантирует что переменная будет инициализирована потокобезопасно и ровно один раз:
Код:
Singleton *instance() {
    static Singleton m_instance;
    return &m_instance;
}

Ну ладно, мы не ищем легких путей. Откроем статью
...
Ну и в качестве барьера видимо надо заюзать
Код
C++ (Qt)
std::atomic_thread_fence(std::memory_order_acquire);
Согласен, хорошее, грамотное решение. Правда не вижу зачем первый-то барьер понадобился?
Можно (и нужно) делать сам указатель атомиком, 8й слайд
Первый барьер нужен ровно для того, для чего mutex::lock() нужно звать и на стороне читателя, и на стороне писателя. "Половинчатый" лок (и барьер) не защищает ни от чего.

И все-таки рискну предложить свою версию (без барьеров)
Код
C++ (Qt)
if (!m_instance) {
   std::unique_lock<std::mutex> l(m_mutex);
   if (!m_instance) {
       auto temp = new Singleton();
       if (temp)
           m_instance = temp;
   }
}
return m_instance;
 
Круто. new не может вернуть 0, а значит компилятор может выкинуть вашу проверку, а значит, это ничем не отличается от кода выше=)

Я тоже Вам привел цитату что при чтении памяти могут быть варианты, но отнюдь не "что угодно"
Ну так и с указателем выше может быть только 0 или не 0, чо никак спасает от краша. Кроме невалидного значения там полно иных проблем, вызывающих UB. Что-то переставилось, что-то осело в регистре, где-то вместо временной переменой компилятор записал напрямую, где-то наоборот, добавил переменную, и всё, понеслась - предсказать результат программы нельзя.
Можно конечно жульничать и рассказывать всем что "у меня же работает" а можно взять и написать программу без UB и не тратить две недели на отладку.
Попытки быть "очень умным" разбиваются о то, что 99% программистов не понимает (включая меня), как работают современные ЦПУ, все знания на "школьном" уровне ассемблера из универа. Вы делаете какие-то предположения об архитектуре не имея не малейшего понятия, как оно там реально устроено. В чем смысл гадания на кофейной гуще? Есть стандарт, который четко говорит что есть UB, а что нет. Пока ваши попытки "сжульничать" с тем же синглтоном выглядят весьма неубедительно.

Я тоже Вам привел цитату что при чтении памяти могут быть варианты, но Ну каждый из этих мифов имеет весомые основания и не раз подтверждался на практике. Поэтому все они и подвержены (массовым) злоупотреблениям, т.е. тыкаются везде и без разбора. Замечу что Ваше использование атомиков тоже очень смахивает на очередной миф  Улыбающийся
Солнце встает на востоке и садится на западе, значит, вращается вокруг земли=) А что, подтверждается же на практике.
Ну и да, я не использую атомики, я использую мьютексы, потому что написание корректного lock-free алгоритма требует те же недели времени. Не верите - загляните в QMutex.
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #31 : Май 30, 2019, 06:09 »

Ну так и с указателем выше может быть только 0 или не 0, чо никак спасает от краша. Кроме невалидного значения...
С "невалидным значением" Вы просто неправы. Обратите внимание на начало примера классиков
Код
C++ (Qt)
Singleton* Singleton::instance ()
{
  Singleton* tmp = pInstance;
...
 
Совершенно бесхитростное чтение указателя из памяти, причем в данный момент этот указатель может писаться др ниткой. Если считанный tmp ненулевой - он принимается и считается верным. Если принять Вашу версию о мусоре - эта конструкция рухнула бы немедленно. Я понимаю что
Цитировать
тестирование может показать наличие багов - но не их отсутствие
И сам имел баги всплывшие через годы, но все-таки вcе хорошо в меру, стремление к абсолютной/идеальной корректности заметно вредит. Заметим что атомарное чтение здесь добавит тормозов но ситуацию никак не изменит - ну да, "не ноль" поймаем быстрее, но если ноль - все то же

...там полно иных проблем, вызывающих UB. Что-то переставилось, что-то осело в регистре, где-то вместо временной переменой компилятор записал напрямую, где-то наоборот, добавил переменную, и всё, понеслась - предсказать результат программы нельзя.
О чем конкретно Вы говорите? О данной реализации синглтона? Не вижу где чего здесь "сдвинется". Как говорят буржуины "please be more concrete"

Можно конечно жульничать и рассказывать всем что "у меня же работает" а можно взять и написать программу без UB и не тратить две недели на отладку.
Попытки быть "очень умным" разбиваются о то, что 99% программистов не понимает (включая меня), как работают современные ЦПУ, все знания на "школьном" уровне ассемблера из универа. Вы делаете какие-то предположения об архитектуре не имея не малейшего понятия, как оно там реально устроено. В чем смысл гадания на кофейной гуще? Есть стандарт, который четко говорит что есть UB, а что нет. Пока ваши попытки "сжульничать" с тем же синглтоном выглядят весьма неубедительно.
Такой хрд мысли очень хорош когда "абсолютно корректное" решение дешево - достаточно лишь покопаться в бааальшой каропке (кросс-платформенной) Улыбающийся Но мне почему-то часто не удается отсидеться у кормушки (наверное не везет).

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

- N ниток заполняют один контейнер (сливают туда рез-ты). Или берут из контейнера задания. Или и то и другое. Для простоты размер контейнера фиксирован. Бум засисяться мутексами или как?
Записан
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3258


Просмотр профиля
« Ответ #32 : Май 30, 2019, 12:34 »

С "невалидным значением" Вы просто неправы. Обратите внимание на начало примера классиков
Код
C++ (Qt)
Singleton* Singleton::instance ()
{
  Singleton* tmp = pInstance;
...
 
Совершенно бесхитростное чтение указателя из памяти, причем в данный момент этот указатель может писаться др ниткой. Если считанный tmp ненулевой - он принимается и считается верным. Если принять Вашу версию о мусоре - эта конструкция рухнула бы немедленно.
Ну так у классиков куча примеров как работать НЕ будет и ни одного как будет (кроме того гипотетического "тут нужен барьер"). На тот момент не было атомиков и "барьер" можно было организовать только директивой процессору/компилятору (тот код что был пару страниц назад). Так как это непереносимо, они не могли сделать адекватный пример.

О чем конкретно Вы говорите? О данной реализации синглтона? Не вижу где чего здесь "сдвинется". Как говорят буржуины "please be more concrete"

Код:
Singleton::Singleton() : m_ptr(new int) {} // вот наш конструктор

if (!m_instance) {
    std::unique_lock<std::mutex> l(m_mutex);
    if (!m_instance) {
        auto temp = new Singleton();
        if (temp)
            m_instance = temp;
    }
}
return m_instance;
Итого, мы делаем
0. lock()
1. m_ptr = new int
2. if (temp) m_instance = temp
3. unlock()
4. if (m_instance)
5. *m_instance->m_ptr;

Зависимости по данным нет, а значит процессор может "переставить":
0. lock()
1. if (temp) m_instance = temp
2. m_ptr = new int
3. unlock()
4. if (m_instance)
5. *m_instance->m_ptr;

Теперь вспоминаем как на самом деле работают барьеры (данные станут видны при следующем ВХОДЕ в барьер, а не ДО выхода). А так как следующего барьера (на чтение) нет, то получаем веселое:
0. lock()
1. unlock()
2. if (temp) m_instance = temp
// барьер нужен ТУТ
3. if (m_instance)
4. *m_instance->m_ptr; // crash
5. m_ptr = new int

Но на x86 у вас этого не будет, потому что STORE-STORE переставлять он не умеет. Поэтому вы не даже не сможете отладить ваш синглтон.

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

- N ниток заполняют один контейнер (сливают туда рез-ты). Или берут из контейнера задания. Или и то и другое. Для простоты размер контейнера фиксирован. Бум засисяться мутексами или как?
Мы уже вроде выяснили, что запись указателя (на примере синглтона) "не атомарна".
Значит, если кто-то пишет, а кто-то потребляет ячейку массива, то нужно предохраняться.

Есть два юзкейза, когда не надо мьютексов.
1. Данные константны и были созданы ДО старта треда (старт треда это барьер). Пример - QApplication.
2. Есть разделение по данным.

Умные люди используют 2. Если тред 1 пишет в ячейку массива 0, а тред 2 - в ячейку массива 1, то атомики и мьютексы не нужны. В ином случае ЛЮБОЕ разделяемое владение должно быть защищено. Даже если это флажок. В случае флажка можно схитрить и заюзать томик вместо мьютекса. В случае шаред_птра/QString уже и атомиком не обойтись, нужен полноценный лок.
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #33 : Июнь 01, 2019, 04:17 »

Зависимости по данным нет, а значит процессор может "переставить":
Правду сказать, не уловил Ваш псевдокод. Но в любом случае если принять версию о мудаковатом процессоре который как хочет так и переставляет, то проблемы конечно будут. Тогда проще всего сделать m_instance атомиком и читать acquire, писать release (тут много ума не надо). НО это ведь никак не связано с первым чтением которое - чистой воды оптимизация. Ненулевое значение по-прежнему "истинно", нулевое по-прежнему "ложно" и его надо перепроверять.

Умные люди используют 2. Если тред 1 пишет в ячейку массива 0, а тред 2 - в ячейку массива 1, то атомики и мьютексы не нужны. В ином случае ЛЮБОЕ разделяемое владение должно быть защищено. Даже если это флажок. В случае флажка можно схитрить и заюзать томик вместо мьютекса. В случае шаред_птра/QString уже и атомиком не обойтись, нужен полноценный лок.
Ну вот напр надо попыксельно пересчитать хороший имедж.
Код
C++ (Qt)
void run( QImage & image, QAtomicInt & rowCount )
{
 while (true) {
  int row = rowCount++;
  if (row >= image.height()) return;
 
  uchat * pix = image.scanLine(row);
  for (int x = 0; x < image.width(); ++x)
    // парим пыксели  
 }
}
Конечно можно было и по-другому, напр дать каждой нитке " с какой строки по какую", но это объективно слабее (нет перераспределения нагрузки). Вот Вам и "lock-free алгоритм".
Записан
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3258


Просмотр профиля
« Ответ #34 : Январь 01, 2000, 05:38 »

Ну так любой map() прекрасно параллелится и не требует синхронизаций:
Код:
y[i] = foo(x[i])

Это известный факт и объясняется тем, что есть разделение по данным - ячейки массива независимы.

Вот reduce уже другое дело.
Записан
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3258


Просмотр профиля
« Ответ #35 : Июль 10, 2019, 16:44 »

Хе-хе, к разговору о мьютексах, атомиках и UB. Обновил gcc с 4.8 до 8.2 код ВНЕЗАПНО стал падать где раньше не падал (при обращении к мемберу из разных тредов).
А вы "ну у меня же работает" Улыбающийся
Записан
ViTech
Гипер активный житель
*****
Offline Offline

Сообщений: 858



Просмотр профиля
« Ответ #36 : Июль 10, 2019, 22:35 »

Обновил gcc с 4.8 до 8.2 код ВНЕЗАПНО стал падать где раньше не падал (при обращении к мемберу из разных тредов).

Пример кода можно? Защита хоть какая была?
Записан

Пока сам не сделаешь...
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3258


Просмотр профиля
« Ответ #37 : Июль 11, 2019, 00:39 »

Обновил gcc с 4.8 до 8.2 код ВНЕЗАПНО стал падать где раньше не падал (при обращении к мемберу из разных тредов).

Пример кода можно? Защита хоть какая была?

Да нет ничего, о том и речь...
Записан
Страниц: 1 2 [3]   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.232 секунд. Запросов: 23.