### threadtask

**Многопоточная задача на PHP: преимущества и особенности**

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

* Репозиторий Docker-контейнера: [docker-threadtask](https://hub.docker.com/repository/docker/talent518/threadtask/general).

### Компиляция и запуск экземпляра кода

1. Для компиляции PHP в режиме безопасности потоков необходимо использовать следующие настройки:
    * Для PHP 8: `--enable-zts`.
    * При компиляции PHP необходимо отключить внешнюю библиотеку GD, то есть не добавлять опцию `--with-external-gd`, вместо этого можно использовать `--with-external-gd=no`.
2. Запустите тестовый скрипт с помощью команды: `./threadtask init.php`.
3. В конфигурации PHP opcache.protect_memory должен быть установлен в 0, иначе будет выдана ошибка сегментации. Это связано с тем, что этот параметр является небезопасным для потоков переключателем.
4. Перезагрузите сигнал SIGUSR1 или SIGUSR2 с помощью команд: `kill -SIGUSR1 pid` или `task_wait(SIGUSR1)` в файле `init.php`.

### Функции

#### 1. Задачи

1. **create_task**: создаёт задачу. Принимает следующие параметры:
    * $taskname — название задачи, которое будет использоваться в целевом потоке для создания константы THREAD_TASK_NAME.
    * $filename — полный или относительный путь к файлу PHP.
    * $params — аргументы командной строки, аналогичные аргументам `arg1`, `arg2`, `arg3`... в команде `php filename arg1 arg2 arg3...`.
    * $logfile — файл журнала для вывода.
    * $logmode — режим открытия файла.
    * $res — ресурс для ожидания завершения задачи.
2. **task_is_run**: проверяет, завершена ли задача. Принимает один параметр: $res, который является ресурсом, переданным из функции create_task.
3. **task_join**: ожидает завершения задачи. Принимает один параметр: $res, который является ресурсом, переданным из функции create_task.
4. **task_kill**: отправляет сигнал $sig задаче. Принимает два параметра: $res (ресурс, переданный из функции create_task) и $sig (сигнал потока).
5. **task_wait**: отправляет сигналы всем потокам и ожидает их завершения. Принимает один параметр: сигнал процесса, например, SIGINT, SIGTERM, SIGUSR1, SIGUSR2.
6. **task_get_delay**: возвращает количество секунд задержки при завершении PHP или прерывании по исключению.
7. **task_set_delay**: устанавливает задержку в секундах перед повторной попыткой выполнения задачи при возникновении исключения во время выполнения. Значение по умолчанию равно 1.
8. **task_get_num**: получает количество потоков или задач. Если используется с параметром is_max, равным true, возвращается максимальное количество потоков, в противном случае — текущее количество потоков или задач. Функция task_get_threads является псевдонимом для task_get_num.
9. **task_set_threads**: устанавливает максимальное количество потоков. Значение по умолчанию — 256.
10. **task_get_debug**: определяет, включена ли отладочная информация.
11. **task_set_debug**: включает или выключает отладочную информацию. Значение по умолчанию — false.
12. **task_get_run**: определяет, выполняется ли программа.
13. **task_set_run**: устанавливает состояние выполнения программы. Значение по умолчанию — true.
14. **pthread_sigmask**: устанавливает маску сигналов для текущего потока. Принимает три параметра:
    * how — тип действия над маской сигналов: SIG_BLOCK, SIG_UNBLOCK или SIG_SETMASK.
    * newset — список сигналов.
    * oldset — выходной параметр, возвращающий предыдущую маску сигналов.
15. **pthread_yield**: освобождает текущий поток от оставшегося времени процессора.
16. **is_main_task**: определяет, является ли текущий поток основным.

#### 2. Общие переменные

1. **share_var_init**: инициализирует общие переменные. Может быть вызвана только в основном потоке. Принимает необязательный параметр size для указания количества переменных.
2. **share_var_exists**: определяет наличие указанной общей переменной.
3. **share_var_get**: считывает общую переменную.
4. **share_var_get_and_del**: считывает и удаляет общую переменную.
5. **share_var_put**: записывает значение в общую переменную. Требуется хотя бы один аргумент, представляющий ключ для поиска многомерного массива. Последний аргумент может объединять существующий массив или заменять его.
6. **share_var_inc**: увеличивает общую переменную на заданное значение. Возвращает результат операции.
7. **share_var_set**: записывает значение в общую переменную.
8. **share_var_set_ex**: записывает значение с заданным сроком действия в общую переменную. Срок действия устанавливается с помощью параметра expire. Значение 0 означает бесконечный срок действия.
9. **share_var_del**: удаляет общую переменную.
10. **share_var_clean**: очищает общие переменные.
11. **share_var_clean_ex**: очищает просроченные общие переменные. Параметр expire должен быть больше 0.
12. **share_var_count**: подсчитывает количество элементов в общей переменной. Возвращаемое значение: больше 0 для массива, меньше 0 для строки, true для объекта, null для несуществующего элемента, false в других случаях.
13. **share_var_destory**: очищает общие переменные, может быть вызвана только в главном потоке.

#### 3. Безопасные для потоков общие переменные

1. **ts_var_declare**: объявляет безопасную для потоков общую переменную. Принимает три аргумента:
    * varname — имя переменной, если пусто, использует значение var.
    * var — переменная, используемая, когда varname пусто.
    * is_fd — если true, можно использовать функцию ts_var_fd.
2. **ts_var_fd**: экспортирует дескриптор файла канала сокета (можно использовать функции расширения sockets). Принимает два аргумента:
    * var — общая переменная, возвращённая функцией ts_var_declare.
    * is_write — указывает, является ли канал каналом записи.
3. **ts_var_expire**: устанавливает срок действия общей переменной. Принимает два аргумента:
    * var — общая переменная, возвращённая функцией ts_var_declare.
    * expire — срок действия, 0 означает отсутствие срока действия.
4. **ts_var_exists**: проверяет наличие общей переменной. Принимает два аргумента:
    * var — общая переменная, возвращённая функцией ts_var_declare.
    * key — ключ, может быть строкой или целым числом.
5. **ts_var_set**: сохраняет данные в общую переменную. Принимает четыре аргумента:
    * var — общая переменная, возвращённая функцией ts_var_declare.
    * key — ключ, строка, целое число или пустое значение, пустое значение добавляет значение к концу.
    * val — значение.
    * expire — срок действия, 0 означает отсутствие срока действия.
6. **ts_var_push**: помещает значения в очередь. Принимает два аргумента:
    * var — общая переменная, возвращённая функцией ts_var_declare.
    * val ... — значения для помещения в очередь.
7. **ts_var_pop**: извлекает последнее значение из очереди. Принимает два аргумента:
    * var — общая переменная, возвращённая функцией ts_var_declare.
    * &key — ссылка на ключ извлечённого значения.
8. **ts_var_shift**: извлекает первое значение из очереди. ```
int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(20) NOT NULL,
  `email` varchar(100) NOT NULL, `password` varchar(32) NOT NULL,
  `salt` varchar(8) NOT NULL,
  `registerTime` datetime NOT NULL,
  `loginTime` datetime DEFAULT NULL,
  `loginTimes` int(11) NOT NULL DEFAULT 0,
  PRIMARY KEY (`uid`),
  UNIQUE KEY `username` (`username`),
  UNIQUE KEY `email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `test`.`user` VALUES
  (1,'admin','admin@test.com','cb13131a512ff854c8bc0dc0ba04e4db','12345678','2019-10-14 22:13:55','2021-03-24 14:49:48',7),
  (2,'test','test@test.com','0ee08e4a9e574f4afa0abfb5ca4e47f8','87654321','2019-10-14 22:13:55','2021-03-24 08:37:56',1),
  (3,'test2','test2@test.com','66b5a5d70de6e691aa9e011eb40bf62c','853532e8','2019-10-16 20:29:18',NULL,0),
  (4,'test3','test3@test.com','093865fe1fc39dedc288275781c12bfe','d03db269','2019-10-16 20:30:10',NULL,0),
  (5,'test4','test4@test.con','94e5d07b62a291858b6cdc902c30f924','cf34c642','2021-03-24 06:40:52','2021-03-24 08:13:17',1),
  (6,'test5','test5@test.com','178a46704b93cd1a6468fe81fc66ae55','f66966f9','2021-03-24 08:17:16','2021-03-24 08:17:54',1);
/* 所有用户的密码都是123456 */
```

**Использование ab для проведения стресс-тестирования, результаты следующие:**

* **Apache + PHP + MySQL + Redis**: 5000 запросов выполнено за 14,264 секунды, пропускная способность составила 350,53, использование памяти превысило 5 ГБ. Если Apache использует mpm_event_module и PHP ZTS, то использование памяти примерно одинаково.

* **ThreadTask + PHP + MySQL**: 5000 запросов выполнено за 4,354 секунды, пропускная способность — 1148,33, использование памяти — 665,07 МБ.

**Заключение**: использование ThreadTask привело к повышению производительности более чем в два раза, что является хорошим результатом.