Все ошибки происходят от полного непонимания как работают потоки.

5th Сентябрь 2014 ;)

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

Вот что это за кошмар?
Не ужеле не понятно что такой код не даёт освобождаться стэку вызовов что в итоге приведёт к его переполнению или к банальной нехватке памяти, т.к. «первая» callb не завершится, пока не завершится вызванная внутри неё «вторая» callb которая не завершится, т.к. она вызывает третью которая не завершится т.к. вызывает «четвёртую» и т.д. стек вызовов будет расти бесконечно! Соответственно все локальные переменные в процедуре тоже будут занимать память для каждой «вложенной» callb т.к. они не будут очищаться в виду того что выхода из процедуры никогда не произойдёт!

Поехали далее.

Так делать никогда, ни при каких обстоятельствах при работе с потоками низззззя!
Это будет приводить к абсолютно рэндомным ошибкам в совершенно разных моментах работы программы! Потому что когда 2 одновременно потока захотят записать что то в переменную, если первый начнёт это делать (пусть это будет integer64 — 8 байта озу, комплексное число в 32 битной яве), и тут у него на пол пути (записал первых 4 байта) заканчивается отведённое ему процессорное время, начинает писать второй поток, он переписывает первые 4 байта, переписывает вторые, чёта там ещё делает. Потом процессорное время снова выделяется первому потоку, он продолжает работать с того места где закончил, он «думает» что первые 4 байта он уже поменял, и меняет вторые. А первые были изменены вторым потоком. Всё! Как минимум у нас в переменной испорченные данные. В случае с jphp всё на много хуже, т.к. переменная тут это сложная структура. При чём в данном php коде она вообще сначала строка. Jphp будет как минимум производить туеву хучу операций по её конвертировании в один из integer типов, этол очень сложные и долгие опрерации с точки зрения процессорных тиков, они не когда не выполнятся в один такт. А значит один поток может начать конвертирование данных в другой тип в то время как второй поток будет их читать! И получит что? ВЕРНО! Обращение к не верному адресу памяти! Т.к. данные строки будут иметь размер 17 байт, а число всего 8! То что тебе повезло при первых 40000 итераций из за медленной работы с сетью (99,999999% времени потоки занимались обработкой данных и работой с сетью), и ещё больше повезло с задержкой не говорит о том что так будет всегда. Рано или позно оно свалится, когда случится ситуация когда 2 потока попытаются изменить переменную и оно сваливалось, просто не сразу.
Каждая переменная, к которой имеют доступ более одного потока, дожна читаться и писаться только внутри критических секций или с использованием мьютексов. (второе больше подходит для работы с ресурсами). Тут даже синхронизацию использовать не реккомендуется (можно получить ситуации взаимной блокировки потоков). Если тебе так сильно надо чтоб переменные были общими между потоками, обязательно читай и записывай их только внутри критической секции. Примерной алгоритм должен быть такой:

вход в критическую секцию
. читаем глобальную переменную в свою, локальную копию
. сразу увеличиваем глобальную перменную чтоб другой поток не работал с теми же данными
выход из кретической секции
весь код потока работающий с КОПИЕЙ глобальной переменной.

Дальше поехали, вот это что за ужас?

Что мы тут делаем? Работает с ресурсом — потоком вывода! Что будет если 2 одновременно потока будут писать в один ресурс — в поток вывода? Да хрень полная будет. Программа свалится. То что тебе повезло, и у тебя не разу не возникло такой ситуации (2 потока одновременно попытались бы писать «Err: ….» в поток вывода), НЕ ЗНАЧИТ что так будет всегда!
Как я уже говорил выше, при работе с ресурсами надо использовать мьютексы, к счастью в данном случае можно тоже ограничиться критическими секциями или даже синхронизацией, т.к. данный ресур уникален для каждого запущенного приложения. Я не буду вдаваться в подробности, почему и как, скажу проще, при работе с физическим устройством например тот же принтер, надо использовать мютексы, при работе с «локальными» ресурсами критические секции. К счастью мы не драйвер пишем, по этому в случае с принтером за нас уже их использовали разработчики драйвера принтера и нам мьютексы тоже не понадобятся.

Копаем следующий код:

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

Что мы тут делаем? одновременно пишем фай из 100 потоков одновременно? И что за каша будет в файле? Хотя кроме каши сначала будет ошибка, т.к. это не разные процессы, а разные потоки использующие один файловый ресурс! Корчое мне писать объяснения надоело, т.к. суть их везде одинаковая, скажу короче так делать нельзя! И тут надо использовать всё теже критические секции ЕСЛИ! Программа не будет запускаться более одной копии. А вот если возможна ситуация с параллельным запуском нескольких копий, то надо использовать либо мьютексы, либо писать файл через конструкцияю fopen — flock — read/write — fclose! Через fopen — flock предпочтительней чем file_get/put_contents. Можно конечно использовать третьим аргументом LOCK_EX и в file_put_contents, но тогда есть вероятность что в момент между file_put_contents и file_get_contents вклинится запись данных другой копии/потока!
Корчое, тут все ошибки только в не правильно написанном алгоритме!
—Конец цитаты—

Спасибо за развернутый ответ, про зацикленность я уже понял, пошел читать про критические секции.


Сегодня: 2016.12.09
jAntivirus