LINUX.ORG.RU

Цикл в thread по mutex или atomic_bool в С++?

 , , ,


0

2

Если нужно просто из цикла условно одновременно во ВСЕХ потоках выходить, то что разумней цикл по мутексу делать или по atomic_bool ??? или вообще это все работает одинаково и на атомике просто кода будет меньше

Оговорюсь С++20 недоступен, поэтому atomic_flag урезан.

Конструкция класса мутекса (заимствовано)

class multithr_off
{
private:
    using guard = unique_lock<mutex>;

    bool _on;
    mutable condition_variable cv;
    mutable mutex lock;

public:
    using ptr_t = shared_ptr<multithr_off>;

    multithr_off() : _on(true) {}

    bool isOn() const
    {
        guard g(lock);
        return _on;
    }

    void off()
    {
        guard g(lock);
        _on = false;
        g.unlock();
        cv.notify_all();
    }

};

auto poff = make_shared<multithr_off>();

Варианты проверок (все имеют тело цикла с обработкой)

1. ожидание вода с клавиатуры (вообще выходит отсюда по isOn!?)
 while ((c = getchar()) != EOF && poff->isOn())

2. ожидание семафора, который отпустят из другого потока
 while (sem_wait(psem) == 0 && poff->isOn())

3. ожидание сброса атомика из другого потока
 atomic_bool abRunRead(false);

 abRunRead.store(true);
 while (abRunRead.load() && poff->isOn())

Честно говоря даже не знаю - проверяются сразу оба условия или последовательно и, например, второе не проверяется если первое ложно.

★★★

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

мьютекс - реализация охраняемых участков

семафор - для вставания в ожидание нечта - простые ожидания неких ивентов.

кондвар - для вставания ожидания нечта с залокнутым мьютексом - для реализации ожиданий на охраняемых участках.

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

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от alysnix

спасибо за развернутый ответ

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

wolverin ★★★
() автор топика
Ответ на: комментарий от wolverin

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

типа

while (!this->_exit_flag) {
  if (wait_something_ms (5000)) ///чего-то ждем c таймаутом 5 сек
  {  ///дождались
    do_something(...);
  }
  else{ ///вышли по таймауту
  
  }
}
alysnix ★★★
()

Сделай просто bool, атомик для такого не нужен. Можешь поставить ему volatile на всякий случай. Атомик нужен когда два потока могут попытаться одновременно записать туда разные значения, и важно кто это сделает первым. У тебя же тупо установка флага безо всяких гонок.

Честно говоря даже не знаю - проверяются сразу оба условия или последовательно и, например, второе не проверяется если первое ложно.

&& не проверяет второе если первое ложно.

firkax ★★★★★
()
Ответ на: комментарий от anonymous2

это таймер, легко пишется в нитке через variadic templates, do_something там в аргументах

timed_wait у всяких там кондваров и семафоров таймер и использует. то есть делать таймер сверху - это просто симулировать ручками timed_wait. смысла в этом нет. и сложней и лишняя сущность.

alysnix ★★★
()
Ответ на: комментарий от firkax

Атомик нужен когда два потока могут попытаться одновременно записать туда разные значения

https://en.cppreference.com/w/cpp/language/memory_model :

When an evaluation of an expression writes to a memory location and another evaluation reads or modifies the same memory location, the expressions are said to conflict. A program that has two conflicting evaluations has a data race If a data race occurs, the behavior of the program is undefined.

Laz ★★★★★
()
Ответ на: комментарий от firkax

Сделай просто bool, атомик для такого не нужен.

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

kvpfs ★★
()
Ответ на: комментарий от Laz

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

firkax ★★★★★
()
Ответ на: комментарий от kvpfs

пойдёт править все эти голые булы

Если кто-то мазохист то мешать ему страдать всё равно не получится.

Ну и так-то запись в бул вообще необязательно атомарна

С чего это неатомарна?

firkax ★★★★★
()
Ответ на: комментарий от Laz

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

С того, что не завёрнута в атомик.

Атомик это про чтение+модификацию. Отдельное чтение и отдельная запись машинных слов и так всегда атомарны.

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 1)
Ответ на: комментарий от firkax

С чего это неатомарна?

Потому что тут три действия: прочитать, модифицировать и записать. Если условно:

bool b = false;
bool ab = false;

thread 1() {
  b = true;
  ab = true;
  ...
}
thread 2() {
  while (! ab) {} 
  assert(b);
  
}

assert может сработать. Про representation trap - это я зря сказал.

kvpfs ★★
()
Последнее исправление: kvpfs (всего исправлений: 1)
Ответ на: комментарий от kvpfs

Разумеется, он может сработать, и даже с большой вероятностью. Между первым и вторым присваиванием дыра, в которой b=true, ab=false. Про то, что атомарно двойное присваивание, никто не говорил, и атомики тебе этого тоже не дадут.

firkax ★★★★★
()
Ответ на: комментарий от kvpfs

Ты отредактировал свой код, а я не успел отредактировать ответ. Вот новый:

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

firkax ★★★★★
()
Ответ на: комментарий от kvpfs

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

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 1)
Ответ на: комментарий от firkax

representation trap - я уже написал, что сказал лишнего, никаких пруфов у меня нет, засчитывается, неатомарность в том смысле, которой пахло от изначального моего поста я не доказываю. Но атомарности у голых булов таки нет, значение можно да хотя бы держать в регистре и записать в самом конце.

kvpfs ★★
()
Ответ на: комментарий от firkax

Атомарность означает что никто не вклинится в середину одного атомарного действия, именно это и ничего более того.

Атомарность означает, что все сторы, которые стоят в коде до атомарной операции, будут видны всем лоадам, которые стоят после неё. Без атомика один тред может вообще никогда не увидеть изменения, сделанные другим тредом.

Laz ★★★★★
()
Последнее исправление: Laz (всего исправлений: 1)
Ответ на: комментарий от Laz

Атомарность означает, что все сторы, которые стоят в коде до атомика, будут видны всем лоадам, которые стоят после него.

Нет, ты привёл прям чётко определение барьера памяти. А что такое атомарность я выше написал.

firkax ★★★★★
()

Разным потокам надо сигналить о необходимости останова по разному. Это зависит от того, в чём спит этот поток. Подход, предлагаемый alysnix – просыпаться в каждом потоке каждую секунду, говорить «зря проснулся», и снова засыпать – это просто впустую аккумулятор расходовать.

Потоки тредпула спят на получении задания из очереди тредпула. Значит у этой очереди должен быть метод stop(), который разбудит все потоки тредпула, спящие в get().

class thread_pool_queue
{
    std::optional<std::function> get();
    void put(std::function &&);
    void stop();
};
iliyap ★★★★★
()
Ответ на: комментарий от firkax

Нет, ты привёл прям чётко определение барьера памяти. А что такое атомарность я выше написал.

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

Laz ★★★★★
()
Ответ на: комментарий от kvpfs

Вредный совет

Это не вредный совет, а нормальный, просто несколько… устаревший, если используете компилятор с новым стандартом языка.

Подойдёт и volatile bool, и std::atomic< bool >. В данном случае используется контракт на явный запрет компилятору всякого рода кеширующих оптимизаций, а нам и нужно чтобы флаг, изменённый в одном потоке, вызывал действительную запись в память, и соответственно сброс кешей во всех остальных. В старом стандарте, это обеспечивалось словом volatile, которое глупые программисты ошибочно использовали для синхронизации, в новом стандарте это одно из свойств std::atomic< bool > - и делает что нужно, и глупые программисты не ошибутся.

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

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

Санитайзер такое не найдёт, потому что это легитимный код.

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

raspopov
()
Ответ на: комментарий от ox55ff

Про компилятор это прям мега верно! Одно из первых с чем столкнулся, когда добавление кода никак не связанного с другим потоком ВДРУГ изменяет поведение других потоков!

wolverin ★★★
() автор топика
Ответ на: комментарий от raspopov

Санитайзер такое не найдёт, потому что это легитимный код.

Блин, ну тут даже стандарт цитировали, с точки зрения языка - код невалидный. https://godbolt.org/z/99TK95hbP.

When an evaluation of an expression writes to a memory location and another evaluation reads or modifies the same memory location, the expressions are said to conflict. A program that has two conflicting evaluations has a data race If a data race occurs, the behavior of the program is undefined.
kvpfs ★★
()
Последнее исправление: kvpfs (всего исправлений: 1)
Ответ на: комментарий от kvpfs

https://godbolt.org/z/99TK95hbP

А-а-а, так ты его специально натравил искать проблемы с потоками. Он в таком режиме в обычной программе тебе столько предупреждений насыпет, что не разберёшься… 😎 Ну, либо всё переписывать заново на C++20…

raspopov
()
Ответ на: комментарий от raspopov

Нет, ничего он не найдёт в нормально написанной программе. Единственная проблема с которой всречался - tsan даёт ложные срабатывания на condition variables, с остальным всё четко работало. А c++20 там только чтобы th.join() два раза не писать, мелочёвка.

kvpfs ★★
()
Последнее исправление: kvpfs (всего исправлений: 1)
Ответ на: комментарий от Laz

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

НЕТ.

Я написал про атомарность. Это абстрактное понятие, к языку отношения не имеющее. Исходя их твоих слов можно предположить, что C11 relaxed-атомики как раз обеспечивают атомарность и только её. А неrelaxed-атомики - атомарность + ставят барьеры памяти вокруг обращений к нему. Но не путай: атомарность - это алгоритмическая абстракция, атомик - языковой объект, имеющий родство с этой абстракцией.

firkax ★★★★★
()
Ответ на: комментарий от her_s_gory

atomic реализован на interlocked инструкциях процессора в отличии от mutex, который объект ядра.

может я отстал от жизни…можно поподробнее про mutex который объект ядра ?

MKuznetsov ★★★★★
()
Ответ на: комментарий от raspopov

На самом деле сработает и обычный bool потому, что…

потому что - потом код начинают дорабатывать и bool превращается.. как обычно брюки превращаются в шорты, шаблон std::atomic хотябы дает понимание другим - что с переменной работают из нескольких потоков, и не важно какая сейчас избыточная реализация std::atomic<bool> в компиляторах.

anonymous2 ★★★★★
()
Последнее исправление: anonymous2 (всего исправлений: 1)
Ответ на: комментарий от MKuznetsov

Я слышал, если совать всё в ядро, то становится только быстрее. В связи с этим предлагаю добавить сисколлы для создания, захвата и освобождения мютексов. Так победим!

hateyoufeel ★★★★★
()
Ответ на: комментарий от firkax

Я написал про атомарность. Это абстрактное понятие, к языку отношения не имеющее.

А, вон он что.. Я-то думал, что речь идёт конкретных вещах - о модели памяти C11 (о чём я несколько раз написал), об атомиках из C++ (о чём был исходный вопрос). Кто ж знал, что ты всё это время вёл беседы об абстрактных понятиях, не имеющих отношения ни к языкам из треда, ни к исходному вопросу в целом.

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

Laz ★★★★★
()
Ответ на: комментарий от hateyoufeel

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

Опоздали чутка: man 2 futex. Последний раз когда смотрел - glibc при захвате своего mutex сначала пыталась обойтись малой кровью и делала несколько попыток взять spinlock и уходила в кернел только если принималось решение «таки надо поспать».

ПыСы: а как это в принципе без помощи кернела и busy-wait loops по Вашему можно сделать?

bugfixer ★★★★★
()
Ответ на: комментарий от Laz

Кто ж знал

Кто ж знал что ты термины не знаешь. А беседы вёл не только я, можешь историю перечитать. Про атомарность начал kvpfs Цикл в thread по mutex или atomic_bool в С++? (комментарий)

Когда ты в следующий раз надумаешь в ответ

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

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 1)