LINUX.ORG.RU

Вопрос про потоки и мьютексы (си)

 


2

2

Здравствуйте. Скажите пожалуйста...

Есть приложение, в нём несколько потоков (выполняют одну и ту же функцию со своими локальными переменными), эти потоки обращаются к глобальной переменной, но не изменяют её, а только читают. Нужно ли в этом случае ставить pthread_mutex_lock() ?


Ответ на: комментарий от anonymous

Если бы ты знал как работают сигналы то не порол бы чушь. У сигналов есть гарантия что они будут вызваны в том же потоке в котором на них навесили handler. Так что любая инструкция которая не прерывается переключенем контекста, будет как бы атомарна с точки зрения _этого_ потока. Как только у тебя в программе появляются другие потоки то для них такой гарантии нет.

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

Эти частные лица хотя бы приводят аргументацию своим словам, а не «во фразе в документации, вырванной из контекста, написано так, значит всегда так и я прав».

Да пусть приводят :-) Есть стандарт ANSI C, есть GNU C Library :-) Есть теория, есть практика :-) Была приведена дока GNU C Library :-) Не веришь, что int на практике атомарный - используй мьютексы и все дела :-) Только не надо мне доказывать, что int не атомарный на моей машине, ссылаясь на рассуждения частного лица, рассуждающего на stackoverflow :-)

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

не порол бы чушь.
как бы атомарна с точки зрения _этого_ потока.

Лол :-) Нет такого понятия «как бы атомарна с точки зрения потока» :-) Атомарность означает неделимость :-) Ни из одного потока в системе нельзя увидеть, что атомарная операция выполнена не полностью :-) Или всё, или ничего :-) Ты бы мат. часть подучил, прежде чем доказывать :-)

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

Уважаемый анонимус, этот код по вашему атомарен на сишке?

Зависит от конкретной платформы :-)

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

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

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

не обучаемый в треде. все под мьютекс

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

Не совсем понимаю контроверзы в ваших утверждениях...

Полагаю, что на практике будут атомарны операции чтения и записи :-) Т.е. i = 100; скорее всего, будет на практике атомарна :-) А что из этого следует, думаю всем понятно, объяснять почему i = i + 1 нужно защищать мьютексом не нужно :-)

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

объяснять почему i = i + 1 нужно защищать мьютексом не нужно

Т.е. объяснять почему i = i + 1 нужно защищать мьютексом не требуется :-) А то ещё поймёшь не правильно :-)

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

А что не так, можно подробнее? Вот тут пример.

int cntr;

void task1(void) 
{
    cntr = 0; 
    
    while (cntr == 0) 
    {
        sleep(1);
    } 
    ...
}

void task2(void) 
{
    ...
    cntr++; 
    sleep(10); 
    ...
}

Если пишет один поток, переменная не может быть в неконсистентном состоянии (это, конечно, зависит от переменной и архитектуры) и небольшое опоздание читающих потоков не критично, то должно нормально работать именно с volatile. Нет?

Никто не говорит, что это хороший подход для больших приложений; но небольшой фрагмент драйвера может так выглядеть.

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

Твой простой пример наверное будет рабоать. Однако самое интересное начинается на чуть более сложном примере:

int result;
int result_is_ready = 0;
void task1(void)
{
    ... // compute result
    result = x;
    result_is_ready = 1;
    ...
}
void task2(void) {
    // wait for result
    while (!result_is_ready) {
        sleep(1);
    }
    x = result; // может выдать мусор вместо результата
}
Вроде бы все операции как бы атомарны но на деле порядок операций не гарантируется и task2 может увидеть что result_is_ready поменялся до того как изменился result.
Если сделать result_is_ready настоящим атомиком с правильно заданным memory_order то такой проблемы не будет.

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

на деле порядок операций не гарантируется и task2 может увидеть что result_is_ready поменялся до того как изменился result

Не помешало бы подкрепить слова пруфцами. В пункт 5.1.2.3 стандарта заглядывал?

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

Так volatile, вроде, гарантирует statement ordering. Так что достаточно сделать result и result_is_ready volatile.

Но пример хороший, да.

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

Не помешало бы подкрепить слова пруфцами. В пункт 5.1.2.3 стандарта заглядывал?

Пункт 5.1.2.4:

9. NOTE 4 There is a separate order for each atomic object. There is no requirement that these can be
combined into a single total order for all objects. In general this will be impossible since different threads
may observe modifications to different variables in inconsistent orders.

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

Так volatile, вроде, гарантирует statement ordering. Так что достаточно сделать result и result_is_ready volatile.

volatile гарантируют только то что компилятор не поменяет порядок записей. А на то что произойдет в кешах процессора volatile повлиять не может, нужны аппаратные барьеры, например `dmb` инструкция на ARM или `lwsync` на PowerPC.

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

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

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

Глобальная int
Дык надо защищать или нет?

Это ты сам должен решить. Если ты согласен, что некоторые потоки могут оперировать не актуальным (устаревшим) значением переменной, то не надо.

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

В glibc int считается атомарным

Атомарность ещё не означает синхронизации.

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

int он и в Африке int :-)

Думаю, не совсем: где-то видел, что только alligned int на практике атомарен. Так что если, например, засунуть его в packed структуру, то этот int будет уже совсем другим int.

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

По ссылке бред какой-то

int stop;
…
stop = 1;  // Says other threads to stop.
…
// Other threads:
while (!stop) {
 ...
}

Compilers assume that programs are race-free (otherwise it’s undefined behavior and all bets are off). So if a program stores to a variable X, the compiler can legally reuse X’s storage for any temporal data inside of some region of code before the store (e.g. to reduce stack frame size).

Если это глобальная переменная, то при чём тут reduce stack frame size? Часто компиляторы используют глобальные переменные для хранения промежуточных результатов?

Если это переменная на стеке, то пусть compiler переиспользует её для промежуточных результатов сколько угодно, у каждого треда свой стек.

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

Если ты согласен, что некоторые потоки могут оперировать не актуальным

То, что данные могут в какой-то момент оказаться не верными НЕ страшно (они будут отсылаться клиенту два раза в сек и он даже не заметит).

Беспокоят другие последствия, например, может ли приложение упасть из-за этого?

...

Поскольку «заварилась» такая каша вокруг «int», тогда если переменная будет:

char bla_bla[10] = {0,};

То мутексы нужны обязательно? Или каждый отдельный байт будет атомарным и пофигу на синхронизацию?

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

Часто компиляторы используют глобальные переменные для хранения промежуточных результатов?

Компиляция кода

/*глобальная*/ int a;
a = f() + g();
в
call f
mov [a], rax
call g
add [a], rax
вполне допустима по стандарту и может быть в некоторых случаях логична.

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

Обращение к а из f/g это UB.

Итак, vzzo считает, что код

a = 1 + a;
содержит UB. Так и запишем.

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

Беспокоят другие последствия

Если ты добавишь volatile к твоему int counter; и будешь запускать только на платформе x86 и amd64, то все будет работать вообще без проблем. Но если ты хочешь сделать твой код корректным с точки зрения стандарта C, и переносимым на другие платформы, такие как arm и mips, то тогда используй атомики или локи.

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

только на платформе x86 и amd64

Нет, будет переноситься на роутер.

Я совсем запутался, ответе пожалуйста по пунктам:

1. volatile - нужно или нет если стоят локи?

2. Может ли быть массив (char bla[20];) volatile ?

3. локи - нужно ставить во всех потоках или только там где он меняет переменную?

4. Что лучше локи или атомарные переменные?

5. Может ли быть (char bla[20];) атомарной переменной?

6. Если переменная атомарная - нужно ли делать её volatile ?

Не сочтите за труд, растолкуйте поподробнее пожалуйста.

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

Часто компиляторы используют

Теоретическая возможность есть (т.к. стандарт не запрещает), следовательно может быть всё что угодно.

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

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

Есть примитивы синхронизации:

  • mutex (mutual exclusion - взаимное исключение);
  • есть семафоры - переменная с функциями инкремента и декремента, если переменная равна нулю, вызов функции декремента останавливает текущую нить(или процесс), до тех пор, пока переменная не станет > 0 (потому что другая нить ее инкрементирует;
  • есть еще другие примитивы/конструкции: барьеры, рандеву и т.п., но это не нужно сейчас

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

То есть, грубо говоря доступ к атомарной переменной представляет из себя захват mutex'а, выполнение операции, освобождение mutex'a. Но поскольку захват mutex'а обычно трудоемкая операция, то реализация в атомарных переменных часто более легковесная, допустим основанная на spin-lock'е.

1. volatile - нужно или нет если стоят локи?

Не нужно для синхронизации вообще. volatile не для этого.

3. локи - нужно ставить во всех потоках или только там где он меняет переменную?

А ты как думаешь, если у тебя есть одноколейка (твоя переменная), нужно ли двум(и более) пытающимся въехать на нее поездам(тредам) светофоры показывать, или достаточно одному?

4. Что лучше локи или атомарные переменные?

атомарные переменные быстрее и удобнее, чем самому управлять mutex'ом.

6. Если переменная атомарная - нужно ли делать её volatile ?

нет

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

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

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

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

Если я использую атомарные переменные, то нужно ли мне компилировать прогу с флагом «gcc -D_REENTRANT» (он мне покоя не даёт), то есть использовать (как я понимаю) библиотеки с потокобезопасными функциями?

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

про употребление слова «лок»

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

А что скажите про это?

Если я использую атомарные переменные, то нужно ли мне компилировать прогу с флагом «gcc -D_REENTRANT» (он мне покоя не даёт), то есть использовать (как я понимаю) библиотеки с потокобезопасными функциями?

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

Я лишь про употребление слова «лок» говорил, оно совершенно неуместное какое-то.

А что не так с этим словом, мне нравится. «Lock - механизм синхронизации, позволяющий обеспечить исключительный доступ к разделяемому ресурсу между несколькими потоками.»
Зачем обязательно говорить какой конкретно примитив ты используешь, mutex или семафор, если можно просто сказать lock и все поймут о чем ты. Разве что любители x86 ассемблера могут подумать о другом, но их можно не брать в расчет.

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

Ох ты ж. Вот достаточно подробно объяснено, что включение флага подразумевает https://en.wikipedia.org/wiki/Reentrancy_(computing)#Rules_for_reentrancy

Ты можешь посмотреть сам, что флаг _REENTRANT примерно делает и что включает:

find /usr/include -type f -exec grep -Hn REENTRANT {} \;

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

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

Механизмов синхронизации поболе чем 2, у половины из них присутствует лок в названии.

все поймут

а может имелось в виду вообще int flag?

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

не писать никакого мультипоточного кода, пока ты их не усвоишь.

Перефразируя Вас получается что - не учись пока не научишься.)

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

Благодарю всех откликнувшихся, пойду читать.

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

что делает флаг -D_REENTRANT при компиляции?

Этот макрос — внутреннее дело Glibc и GCC, ты его не должен никогда указывать. Для работы с потоками в GCC нужно использовать флаг -pthread (не -lpthread).

i-rinat ★★★★★
()
Ответ на: комментарий от i-rinat

Спасибо. А объясните пожалуйста, в чём разница -pthread и -lpthread, до Вашего коммента компилил вот так:

gcc -Wall -Wextra  websocket.c -o websocket -lpthread -lcrypto
stD
() автор топика
Ответ на: комментарий от stD

man gcc

-pthread
Adds support for multithreading with the pthreads library. This option sets flags for both the preprocessor and linker.

Т.е. gcc позаботится не только об указании библиотеки -lpthread для линкера, но и о необходимых опциях для препрцессора, как _REENTRANT. Т.е., как правило, -pthread == -D_REENTRANT -lpthread.

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

Т.е. gcc позаботится не только об указании библиотеки...

Огромное благодарю.

stD
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.