Опрос: как у вас решается проблема синхронизации параллельных запросов на PHP?

    Долгое время я пытаюсь понять, сколько места отведено вопросам параллельности и конкурентности выполнения кода в повседневной практике среднестатистического PHP-программиста. С одной стороны, разрабатывая серверное приложение, программист автоматически пишет код, который будет выполняться параллельно. С другой, на практике в PHP все проблемы этой области решались инструментами, которыми все пользовались — веб-сервер, сессия и СУБД.

    Обращают ли на ваших проектах внимание на на проблемы синхронизации параллельно обрабатывающихся HTTP-запросов? Они решаются через транзакции, блокировки? Какие способы блокирования вы используете? Да и вообще, нужно об этом париться, или тема бесполезная? Узнаем мнение аудитории. Этот пост не дает ответов на вопросы. Здесь ведется разведка.


    ***

    В мире PHP исторически принято не обращать много внимания на параллельность выполнения кода. Под многопоточность не заточен сам PHP (нет потокобезопасности внутри движка). Я ни разу не встречал проекты или программистов, которые использовали pthreads (а вы встречали? тогда расскажите об этом в коменнтах). И не очень-то именно многопоточность нужна на практике в веб-приложениях, где параллельно нужно выполнять прежде всего запросы, а не отдельные части кода в рамках одного запроса. А так как параллельное выполнение входящих запросов в отдельных процессах организуется сервером приложений (php-fpm или apache), программисту об этом думать не нужно — все работает из коробки.

    В PHP есть механизм сессий, которым пользуются в подавляющем большинстве случаев. А сессия с дефолтовыми настройками блокирует параллельное выполнение запросов в рамках одной сессии. Это «прикрывает» некоторые дыры и приводит к тому, что на практике можно никогда и не столкнуться с явными проблемами. Т.е. пока пользователь не начнет хакерить, работая, например, в двух браузерах одновременно, ничего не сломается из-за отсутствия блокировок и транзакций.

    К тому же вероятность коллизий из-за параллельности очень мала для сайтов с пиковым количеством запросов меньше чем 2 в секунду (исходя из того, что время генерации ответа не превышает секунду).

    Ну и, наконец, если какие-то проблемы параллельности все же всплывают, то самый простой способ их решения — транзакция в БД с достаточным уровнем изоляции. Так как почти все сайты, которые разрабатываются на PHP, используют в качестве хранилища транзакционную СУБД, достаточно просто начать использовать транзакции для решения проблем конкуренции выполнения запросов, которые приводят к неконсистентности данных. Даже не вникая глубоко в тему синхронизации процессов, просто используя транзакции, проблему можно решить.

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

    Отчасти все это и хорошо — низкий порог вхождения, что и является одним из главных преимуществ PHP. Быстрая разработка за счет экономии времени на проработку всех узким мест. Но рано или поздно, многие начинают сталкиваться с проблемой синхронизации процессов лицом к лицу. А разработка надежных, а не рассчитанных на обстоятельства, приложений требует определенного уровня изучения этого вопроса.

    Самый простой пример, когда не поможет транзакция, — прогрев кэша. Чтобы данные, которые кэшируются, не генерировались параллельно, конкурентные запросы нужно блокировать, дав заполнить кэш тому запросу, который начал это делать первым. Без блокировки тут не обойтись. Причем, если серверов несколько, блокировка должна быть централизованной. Другой пример, файловый хостинг. У пользователя ограничено количество файлов, которые он может закачать. При добавлении файла нужно сравнить количество закачанных файлов с лимитом и принять файл, если лимит не исчерпан. Хотя тут можно сделать финт ушами и обойтись без блокировок, проще всего будет заблокироваться перед проверкой по пользователю, проверить счетчик, занять слот под файл, снять блокировку, после чего принять само тело файла.

    И с использованием транзакций тоже есть свои проблемы. Как минимум то, что их надо несколько раз рестартовать, если присутствует race-condition и транзакция откатывается по причине коллизии. Есть вопросы при работе с внешними для БД ресурсами — файлами, кэшем, запросами к удаленному API.

    ***

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

    Как эта проблема решается у нас?
    У нас используются транзацкии и блокировки. Транзакции помогают сохранить консистентность данных, если выполнение задачи сводится к серии запросов в БД. Когда же нужно синхронизировать код, который работает не только с базой, или вообще с ней не работает, мы используем блокировки через мою библиотеку абстракции над способом блокировки. Если бэкэнд работает на одном сервере, достаточно использовать драйвер для flock(), если нужно блокироваться распределенно, то можно использовать драйвера для Redis или Memcache.


    Если у вас есть хорошие материалы по этой теме, делитесь ссылками в комментах.

    P.S. Любителям других языков программирования: если вы хотите рассказать, как проблема успешно решается в вашем языке/фреймворке, you are welcome. В противном случае, обратите внимание на хаб, в котором размещена публикация.
    Вы программируете серверную часть приложения:
    Актуальны ли по вашему мнению вопросы межпроцессного взаимодействия и синхронизации для PHP-программиста?
    Актуален ли на вашем проекте (на работе) вопрос синхронизации параллельных запросов (процессов)?
    Какие способы синхронизации вы используете?

    Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

    Поделиться публикацией
    Похожие публикации
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 47
    • +1
      Мы для синхронизации потоков в критичных местах использовали семафоры Linux, надежнее некуда. В остальных случаях (чаще) используем блокировки через Memcached.
      • +16
        Вы смешиваете две совершенно разные проблемы.

        Одна из них — это «одновременное» обращение двух потоков выполнения к одному и тому же ресурсу, когда этот ресурс такого обращения не позволяет (например, получение последовательных номеров; чуть хуже как пример — ваше заполнение кэша). Тут действительно нужна сериализация (т.е., выстраивание в очередь) параллельных запросов

        Совершенно другое дело — коллизии внесения изменений (ваш пример с гитхабом). Тут ведь дело не в параллелизме как таковом, тут дело в том, что данные, использовавшиеся для создания изменений, устарели на момент их внесения. И тут сериализация не поможет, потому что в большей части случаев никто вам не даст устраивать пессимистические блокировки данных на все время работы пользователя, а, значит, нужно переходить на совершенно другие примитивы, нежели простая синхронизация.

        И, кстати, пример с гитхабом наглядно показывает, что коллизии внесения изменений однозначного решения не имеют: благодаря тому, что гитхаб — это версионное хранилище, никто не мешает просто создать два независимых коммита (точнее, две staging area), выбор между которыми (если он вообще нужен) пользователь сделает самостоятельно. В типовых LOB-системах это совсем не так тривиально.
        • 0
          На примере гита я хотел показать другое, не вопрос потери данных. Но пример действительно получился плохим. Надо придумать другой…
          • 0
            Про гит удалил пример, добавил про счетчик. Интересно, что скажете по этому поводу?
            • 0
              С примером под счетчик все вообще тривиально: там нужно отслеживать количество свободных слотов + количество идущих закачек, пока они одно меньше другого, позволять закачивать заново.
              • 0
                Нет, не тривиально. Несколько запросов параллельно могу сравнить и получить разрешение на закачку. И, соответственно, закачать. Решение здесь есть через UPDATE… fact=fact+1… WHERE id = x AND limit < fact, а потом посмотреть affected rows — если оно больше нуля, то принять закачку. Но это не тривиальное решение. И я не думаю, что все повально им пользуются.

                Причем, это вообще будет работать только если есть fact. А если нет, то нужно вести подсчет на лету, и это уже не прокатит.
                • 0
                  Несколько запросов параллельно могу сравнить и получить разрешение на закачку.

                  Так каждый запрос должен получать разрешение и уменьшать лимит в рамках одной транзакции.
                  • –1
                    Ниже уже разобрались, что для MySQL с уровнем изоляции по умолчанию это не так.
                    • +2
                      Так надо использовать не «по умолчанию», а подходящий для задачи.
                      • 0
                        Ну то есть либо выбрать другой уровень изоляции (для MySQL в данном случае потребуется SERIALIZABLE со всеми вытекающими), либо выбрать другое решение.
                        • 0
                          Зачем вам serializable, если вам достаточно read committed (даже не repeatable read!) + select with shared lock? И какие «все вытекающие» здесь будут, по-вашему?
                          • –1
                            Вытекающие будут из serializable. А на счет read committed + select with shared lock, это уже несколько другая плоскость. Тут требуются явные дополнительные действия (как и в случае с обычными писсимистическими блокировками вне БД). Ну и требуется отлавливать падения на дедлоках (именно так оно выглядит в случае MySQL) и запускать транзакцию снова и снова, пока она таки не пройдет успешно. Об этом надо как минимум знать. И это не всегда удобно. А у 51 % опрошенных «этот вопрос вообще никак не стоит».

                            Оба решения имеют свои плюсы и минусы. Оптимистические блокировки не всегда лучше, чем писсимистические. А когда мы выходим за пределы БД, использование транзакций для синхронизации может стать проблемой. Так что в идеале нужно выбирать каждый раз индивидуально. И с этим могут быть проблемы, потому что мало кто из PHP-программистов на столько хорошо владеет темой, как, например, вы.
                            • +1
                              Вытекающие будут из serializable.

                              Какие именно?

                              А на счет read committed + select with shared lock, это уже несколько другая плоскость. Тут требуются явные дополнительные действия

                              Это какие же? Прописать with shared lock в sql-команду?

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

                              … как и в случае потери связи с СУБД. В норме у вас не должно быть дедлоков.

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

                              Вот поэтому транзакции используются не для синхронизации, а для обеспечения консистентности данных.
          • +2
            Я думаю, что как только php-разработчик начинает писать хотя бы cron-сервис, то он уже сталкивается с проблемой коллизий. Самый банальный пример: вызов сервиса раз в минуту и ситуация, когда время его выполнения превышает эту минуту. Те, кто не пишет приложения, вызываемые из CLI, пожалуй редко сталкиваются с подобными проблемами.
            У нас в проекте, к примеру, все cron-сервисы имеют блокировку. Также работают любые финансовые операции в системе. Есть разумеется и обёртывание в транзакции. Есть также и сервисы для callback запросов, там тоже приходилось ставить блокировки во избежании коллизий.
            Но кстати, для регистрации блокировок используется БД, а не memcached. В Redis необходимости не было, а ради одной такой задачи ставить целый сервис смысла нет.
            • 0
              Я в случае подобных cron сервисов просто делаю проверку не запущен ли уже процесс. Если запущен, то выхожу из текущей копии и все.
              • 0
                Можно и так.
                В нашем случае есть абстрактный класс, имеющий в себе механизм блокировки, от которого наследуются классы сервисов (а их сейчас около 15). Запускается это например как «php cron.php --s=mailSender». Поэтому проверять наличие процесса в памяти уже было бы не слишком удобно. А использовать запись/проверку pid в файлы в общем-то те же яйца, что таблица блокировок в БД.
              • 0
                Если процессы запускаются на одной машине — то самый простой вариант реализации мьютекса — flock. Проверяем — можем ли получить lock — остальное за нас сделает *nix.
              • 0
                Вот интересно. Большая часть участников опроса полагается на транзации. Гораздо меньше людей используют блокировки. 55 % сообщают, что вопрос блокировок у них не стоит вообще. Но при решении проблемы с файлом, которая приведена в примере (т.е. проблема счетчика-ограничителя в общем случае), транзакция поможет только при уровне SERIALIZABLE. А это убивает производительность. Кроме того, по умолчанию в MySQL стоит REPEATABLE READ. Не думаю, что все повально его меняют на SERIALIZABLE. А это очень распространенная задача.

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

                Воспроизвести пример и поэксперементировать с ним можно с помощью этого:
                test.php
                test.sql

                Это очень странно, учитывая, что 40 % опрошенных отметили, что разбираются в теме.

                Как задача счетчика-ограничителя решается у вас?
                • +2
                  Я не понимаю, почему вы противопоставляете транзакции и блокировки. Транзакция внутри себя использует блокировки. Соответственно, serializable-транзакция — это пессимистичная блокировка со всеми вытекающими отсюда (например, потерей производительности при одновременных запросах). Если вам нужна повысить производительность — вы переходите на оптимистичные сценарии блокировки (с проверкой условий в момент завершения транзакции), при этом усложняя логику и повышая вероятность отказа транзакции на поздних этапах.

                  Кстати, в задаче со счетчиком достаточно уровня изоляции repeatable read.
                  • –1
                    Транзакция внутри себя использует блокировки

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

                    Кстати, в задаче со счетчиком достаточно уровня изоляции repeatable read.

                    Не достаточно. Очень легко запустить приведенный по ссылкам выше код на любой машине, где есть PHP и MySQL. А после просто удалить файл и выполнить блок очистки, чтобы мусора не осталось. Попробуйте запустить его с уровнем REPEATABLE READ.
                    • +2
                      Вообще, исходная задача транзакции несколько иная — обеспечить возможность отката пачки изменений. А не синхронизация.

                      Транзакция — это ACID. Вы сейчас говорите только про atomicity. А синхронизация — это consistency/isolation. Так что (правильно примененная) транзакция — это блокировки + еще что-то.

                      Не достаточно. Очень легко запустить приведенный по ссылкам выше код на любой машине, где есть PHP и MySQL. А после просто удалить файл и выполнить блок очистки, чтобы мусора не осталось. Попробуйте запустить его с уровнем REPEATABLE READ.

                      У меня нет ни PHP, ни MySQL. Но, если на пальцах, то проблемы возникать не должно, потому что repeatable read в момент первого селекта должен заблокировать запись на чтение для всех, кроме этого потока. Теперь добавим второй поток (для простоты, его селект происходит всегда после селекта в первом потоке). У нас получается два сценария:

                      1. инсерт (второй, первый никому не интересен) первого потока случается раньше второго. Этот инсерт напарывается на блокировку, выставленную вторым потоком при селекте, и начинает ждать, пока ее отпустят. Дальше случится одно из двух (в смысле, одно случится раньше другого): либо закончится таймаут, инсерт провалится, транзакция откатится, либо второй поток тоже запустит инсерт. Надо понимать, что инсерт второго потока тоже наткнется на блокировку (выставленную первым потоком), и дальше уже случится дедлок, который либо вывалит первую (она раньше) транзакцию с таймаутом, либо будет разрешен движком СУБД в любую сторону.
                      2. инсерт второго потока случится раньше первого. Он напорется на блокировку, выставленную первым… а дальше все аналогично первому сценарию, либо таймаут, либо дедлок.


                      Что характерно, инвариант будет сохранен в обоих случаях.

                      Repeatable read не работает, когда у вас проверки идут поверх диапазона строк (например, вы считаете фактически загруженные файлы как число строк для данного пользователя). Вот там нужен serializable.

                      Но это мы еще не переходили к оптимистическим блокировкам вообще и их реализации на основе версионирования строк в частности (SNAPSHOT isolation и READ COMMITTED SNAPSHOT).
                      • 0
                        REPEATABLE READ ничего не блокирует. А SERIALIZABLE блокирует. Об этом написано. И это прослеживается на практике. Ниже в спойлере копипаст из консоли mysql. После первого селекта в другой консоли было изменено значение fact на 2. Как видно, UPDATE в данной странзакции сделал его 3-кой.

                        Копипаст из консоли mysql
                        mysql> START TRANSACTION;
                        Query OK, 0 rows affected (0.00 sec)
                        
                        mysql> SELECT `limit`, fact FROM users WHERE id = 1;
                        +-------+------+
                        | limit | fact |
                        +-------+------+
                        |     2 |    1 |
                        +-------+------+
                        1 row in set (0.00 sec)
                        
                        mysql> UPDATE users SET fact = fact + 1 WHERE id = 1;
                        Query OK, 1 row affected (0.00 sec)
                        Rows matched: 1  Changed: 1  Warnings: 0
                        
                        mysql> COMMIT;
                        Query OK, 0 rows affected (0.00 sec)
                        
                        mysql> SELECT `limit`, fact FROM users WHERE id = 1;
                        +-------+------+
                        | limit | fact |
                        +-------+------+
                        |     2 |    3 |
                        +-------+------+
                        1 row in set (0.00 sec)
                        



                        И я еще раз подчеркиваю, что да, возможности синхронизации через БД тут есть, но:
                        1. они не работают по умолчанию, т.е. нужно менять уровень изоляции;
                        2. требуется такой уровень изоляции, который под нагрузкой все уложит и преведет к дедлокам на дедлоке в случае MySQL.


                        Так что не делая здесь хоть какую-то явную блокировку, получаем дырку. А теперь вопрос, в каком количестве случае это делается правильно, а об этом даже не думают?
                        • +2
                          REPEATABLE READ ничего не блокирует.

                          А, так это передайте привет конкретному MySQL, у которого REPEATABLE READ по умолчанию работает через снепшоты (т.е. заточен под оптимистическую блокировку). Там, кстати, дальше написано, что с этим делать: FOR UPDATE или LOCK IN SHARE MODE (второй приводит REPEATABLE READ к стандартному поведению, которое я и описал).

                          Вот вам цитата из доки на MS SQL (выделение мое):
                          REPEATABLE READ

                          Specifies that statements cannot read data that has been modified but not yet committed by other transactions and that no other transactions can modify data that has been read by the current transaction until the current transaction completes.


                          они не работают по умолчанию, т.е. нужно менять уровень изоляции;

                          Они не работают по умолчанию на MySQL, есть разница.

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

                          … в случае любой БД, если реализовать криво.

                          Понимаете ли, в чем дело, ACID-транзакции и «под нагрузкой» — это несколько конфликтующие вещи. И вообще блокировки и нагрузка друг другу противоречат. Поэтому, как только у вас появляется существенная нагрузка, нужно думать о том, как будут себя вести ваши блокировки.

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

                          Это, повторюсь, специфика конкретного MySQL. Делая все то же самое на MS SQL, «дырки» вы не получите; а делая все то же самое на EntityFramework поверх MS SQL, можете получить еще и работающую оптимистичную блокировку взамен пессимистичной. Вопрос выбираемых вами инструментов и понимания их работы.
                          • –1
                            В MySQL много чего кривого, но только работает она быстро и жрет относительно мало. Плюсов тоже хватает. И ей дефакто пользуются практически все PHP-девелоперы. А весь топик посвящен именно PHP-девелоперам.

                            Можно углубляться сколько угодно, только вот у задачи со счетчиком есть конкретное решение, которое будет прекрасно работать и под нагрузкой (и я не согласен, что нагрузка и блокировки не совместимы). Так как в данном примере мы блокируем только одного пользователя, и это защита от взлома, она не будет проявляться при нормальной работе. Так вот достаточно добавить ограничение на количество открытых динамических запросов с одного IP и/или от одного пользователя. Первые N запросов от него действительно будут поедать память сервера и «висеть» на блокировке, но после превышения лимита запросы будут сразу отваливаться. А ожидание блокировки должно быть определено таймаутом, после которого и ждущие будут тоже отваливаться. Ну и не нужно делать блокировки, которые будут висеть десятки секунд. И транзакции тут тем более не подойдут. Когда время ожидания большое, нужно действовать асинхронно.

                            Сделав все правильно, оно будет хорошо работать под большой нагрузкой, под которую вообще имеет смысл писать PHP-приложение. Вон тот же Хабр, например.
                            • 0
                              у задачи со счетчиком есть конкретное решение, которое будет прекрасно работать и под нагрузкой

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

                              Так вот достаточно добавить ограничение на количество открытых динамических запросов с одного IP и/или от одного пользователя.

                              … и как вы его сделаете без транзакции?

                              И транзакции тут тем более не подойдут.

                              Просто готовьте их правильно, и все будет хорошо.

                              Хотя да, с тем, что «если все сделать правильно, будет хорошо работать под большой нагрузкой», я не спорю.
                              • 0
                                Так вот достаточно добавить ограничение на количество открытых динамических запросов с одного IP и/или от одного пользователя.
                                … и как вы его сделаете без транзакции?

                                Т.к. здесь допустима погрешность, при можно применить гарантированно быструю операцию без синхронизации. Но вообще, если уже придираться, то можно использовать атомарный инкремент в Memcache или Redis. Ну а можно и просто писать в общую память и использовать примитив синхронизации, если сервер один.

                                У меня такая штука сделана без синхронизации и без транзакции, я ее тестил через ab, результаты были очень хорошие.
                                • 0
                                  Т.к. здесь допустима погрешность

                                  Если погрешность допустима, то можно просто использовать неблокирующее решение, в чем проблема-то?

                                  Но вообще, если уже придираться, то можно использовать атомарный инкремент в Memcache или Redis.

                                  … который фактически тоже транзакция, просто без D.

                                  Ну а можно и просто писать в общую память и использовать примитив синхронизации, если сервер один.

                                  … аналогично.
                                  • 0
                                    Если погрешность допустима, то можно просто использовать неблокирующее решение, в чем проблема-то?

                                    Погрешность допустима при подсчете количества запросов, которые могут работать одновременно для пользователя и/или IP. Будет их 10 или 12, разницы никакой (ну ~100 Кб памяти разница на время таймаута блокировки). А вот если применить неблокирующее решение задачи, то мы получим взлом системы — юзер загрузит больше файлов, чем можно. Это уже проблема. Ну или я неправильно вас понял. Потому что если под неблокирующим решением вы понимали lock-free алгоритмы, т.е. тот же CAS, то такое решение я уже выдавал в самом начале — UPDATE table SET fact = fact + 1 WHERE id=1 AND limit > fact + affected rows.
                                    • 0
                                      Погрешность допустима при подсчете количества запросов, которые могут работать одновременно для пользователя и/или IP. Будет их 10 или 12, разницы никакой (ну ~100 Кб памяти разница на время таймаута блокировки).

                                      Ну то есть у вас на самом деле внутри все равно честная транзакция с блокировкой, просто вы снаружи поставили дополнительную, более дешевую блокировку, чтобы уменьшить количество потенциально заблокированных потоков внутри. Так тоже можно, только вы сложность системы увеличили вдвое, тем самым удорожив поддержку — а тут уже вопрос, что дешевле, ускорять транзакции или тратить на поддержку.
                                      • 0
                                        Истину глаголите в частном случае. Но в общем случае, надо отметить, что данное ограничение на количество висящих коннектов у меня существует не для и не только из-за блокировок. Даже если бы не было ни единой блокировки, все равно была бы эта защита от DoS. Т.е. я не плачу дополнительно, а просто пользуюсь и так работающим функционалом.
                                        • 0
                                          … а внутри у вас обычные транзакции с блокировками. Что как бы демонстрирует нам, что это нормальное работающее решение.
                                      • НЛО прилетело и опубликовало эту надпись здесь
                                        • 0
                                          Ага, по крону удалить успешно закачанный файл…

                                          А представьте, что мы не файлы закачиваем, а серверы выдаем. Или что-то еще дороже.

                                          Нет здесь косяка — надежное соблюдение бизнес-логики — это обязательное требование. Или можете считать так: в моем примере это именно обязательное требование со стороны бизнеса.
                                          • 0
                                            Вы про eventual consistency не слышали?
                          • 0
                            А вот при попытке повторить тоже самое при SERIALIZABLE я уже как и ожидалось получаю блокировку, а в итоге второй запрос получает:

                            #1213 - Deadlock found when trying to get lock; try restarting transaction 
                            


                            Причем, запрос на модификацию значения во второй транзакции висит на блокировке, пока не я изменю в первой транзакции значение. А как только выполнил UPDATE, в тот же момент вторая транзакция отвалилась с обозначенным выше сообщением о дедлоке.

                            Опять же копипаст из консоли
                            mysql> SET SESSION tx_isolation='SERIALIZABLE';
                            Query OK, 0 rows affected (0.00 sec)
                            
                            mysql> START TRANSACTION;
                            Query OK, 0 rows affected (0.00 sec)
                            
                            mysql> SELECT `limit`, fact FROM users WHERE id = 1;
                            +-------+------+
                            | limit | fact |
                            +-------+------+
                            |     2 |    3 |
                            +-------+------+
                            1 row in set (0.01 sec)
                            
                            mysql> UPDATE users SET fact = fact + 1 WHERE id = 1;
                            Query OK, 1 row affected (0.00 sec)
                            Rows matched: 1  Changed: 1  Warnings: 0
                            
                            mysql> COMMIT;
                            Query OK, 0 rows affected (0.00 sec)
                            
                            mysql> SELECT `limit`, fact FROM users WHERE id = 1;
                            +-------+------+
                            | limit | fact |
                            +-------+------+
                            |     2 |    4 |
                            +-------+------+
                            1 row in set (0.00 sec)
                            
                            mysql>
                            



                            Так что точно могу сказать, что здесь все совершенно не тривиально, как это кажется изначально. SNAPSHOT isolation и READ COMMITTED SNAPSHOT в MySQL не. (MySQL — это default СУБД для PHP-разработчика.)
                            • 0
                              READ COMMITTED SNAPSHOT в MySQL не.

                              Как раз наоборот, он включен там по умолчанию, из-за чего вы и получаете поведение, описанное в документации.
                      • +2
                        Могу ошибаться, но в вашем случает стоит использовать SELECT FOR UPDATE.
                      • 0
                        > Я ни разу не встречал проекты или программистов, которые использовали pthreads
                        Он слишком сырой, только чтобы поиграться. Через несколько лет возможно появится в проектах.

                        А блокировки можно делать штатными средствами MySQL, без flock и т.д.
                        • 0
                          А блокировки можно делать штатными средствами MySQL, без flock и т.д.

                          Ну это если MySQL есть :) Ну и возможности блокировок там все же ограничены.

                          Через несколько лет возможно появится в проектах.

                          Спрос очень мелкий… А проблем оно может притянуть массу. Так что может и не появиться. Вот какие реальные юзкейсы, где многопоточность выиграет у многозадачности (=многопроцессности)?
                          • 0
                            Вот какие реальные юзкейсы, где многопоточность выиграет у многозадачности (=многопроцессности)?

                            Проверка статуса выполнения задачи на n удаленных агентах.
                            • –1
                              Если это веб-интерфейс, то скорее всего лучше будут асинхронные запросы на проверку каждого агента отдельно. Тогда если один или несколько агентов будут долго отвечать, это будет видно интерактивно.

                              А вот если это консольное приложение, демон (сервис)… Тут момент такой, что ждать ответа по сети можно и асинхронно. Для этого не нужно заводить треды. Тот же eventloop отлично подойдет. Особенно, если агентов много (ну сотни, например). Ну под линуксом во всяком случае. Треды они все же нужны для активной работы, а не пассивной, имхо. Но тут я могу быть неправым, т.к. сам-то их никогда почти и не юзал…
                              • 0
                                Если это веб-интерфейс, то скорее всего лучше будут асинхронные запросы на проверку каждого агента отдельно. Тогда если один или несколько агентов будут долго отвечать, это будет видно интерактивно.

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

                                Тут момент такой, что ждать ответа по сети можно и асинхронно.

                                Его и в веб-интерфейсе можно ждать асинхронно. Только немедленно возникает вопрос — а нужно ли программисту думать над собственной реализацией событийной модели, или можно как-то иначе себя развлечь. И тут немедленно начинает выигрывать тот фреймворк, в котором computation-bound work и IO-bound work реализованы через одну абстракцию.

                                Ну и да, вычислительные работы тоже случаются иногда.
                              • 0
                                Как вариант решения для данного задачи можно использовать ReactPHP лишь в одном процессе. Тут больше нужна асинхронность, нежели многопроцессность.

                                upd: уже ответили, опоздал.
                          • +2
                            Автору спасибо! Хорошую тему создал.

                            Но проблема освещена с неверного угла.
                            В вебе существует сервер и множество параллельных клиентов. Они дерутся за ресурсы.
                            Чтобы данные были согласованы при параллельном доступе необходимо транзакции.
                            Существуют 2 типа транзакций: системные транзакции(БД, API, etc) и бизнес транзакции.

                            В большинстве случаев хватает 1 системной транзакции на 1 запрос клиента. Иногда в запросе может быть несколько транзакций.

                            Но большинство проблем начинается тогда, когда необходима 1 транзакция на несколько запросов.
                            В данном случае на 1 бизнес транзакцию приходится несколько запросов и соответственно несколько системных транзакций.
                            На помощь приходят паттерны Optimistic Offline Lock и Pessimictic Offline Lock.

                            Первый паттерн великолепно реализован в PHP Doctrine 2(смотрите в документации Doctrine 2 ORM 2 documentation — 13.2.1. Optimistic Locking).
                            Необходимость второго паттерна возникает редко. Когда возникает — реализую в минимально простом виде(в двух словах паттерн не описать, погуглите).

                            Т.е. в общем и целом всё отлично в PHP с параллелизмом!
                            • –2
                              Знаете, прижелании можно и в NodeJS впарить пногопоточность. Но зачем?

                              П. С. image
                              Коммюнити PHP такое коммюнити. А ведь парень дело говорит :/ Я и сам сначала подумал, что это просто биндинги под `pthread`.
                            • +2
                              На мой взгляд, большинство разработчиков не вникает в этот вопрос, так как большинство проектов на рынке не требуют консистентность данных. Разумеется, консистетность является желательной в 100% проектов, но при этом в 95% из них она нужна не настолько сильно, чтобы тратить ресурсы на её поддержание.
                              То есть на вопрос «почему разработчики не уделяют внимание параллельным запросам» самый простой ответ — это не востребовано рынком.

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