ФорумПрограммированиеПыхнуть хотите?F.A.Q. → Эмуляция многопоточности в PHP

Эмуляция многопоточности в PHP

  • sap

    Сообщения: 2701 Репутация: N Группа: Кто попало

    Spritz 19 сентября 2008 г. 11:32

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

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

    Я рассмотрю простой пример, как можно достичь эмуляции многопоточности в PHP.

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

    $res = mysql_query('SELECT `email` FROM `user`');

    while ($row = mysql_fetch_assoc($res))
    mail($row['email'], $theme, $text);


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

    $res = mysql_query('SELECT `email` FROM `user`');

    $data = array();

    while ($row = mysql_fetch_assoc($res))
    $data[] = $row;

    $multithreading = new MultiThreading();

    // mailer.php — скрипт, который отправляет письмо по адресу, переданному ему методом GET,
    // то есть [email protected]
    $multithreading->setScriptName('mailer.php');

    $multithreading->setParams($data);

    $multithreading->execute();


    Все просто и удобно. Осталось написать соответствующий класс, что я и сделал. Вот он, со всеми пояснениями в комментариях.

    class MultiThreading
    {
    /**
    * Имя сервера
    *
    * @var string
    * @access private
    */
    private $server;

    /**
    * Максимальное количество потоков
    *
    * @var int
    * @access private
    */
    private $maxthreads;

    /**
    * Имя скрипта, который выполняет нужную нам задачу
    *
    * @var string
    * @access private
    */
    private $scriptname;

    /**
    * Параметры, которые мы будем передавать скрипту
    *
    * @var array
    * @access private
    */
    private $params = array();

    /**
    * Массив, в котором хранятся потоки
    *
    * @var array
    * @access private
    */
    private $threads = array();

    /**
    * Массив, в котором хранятся результаты
    *
    * @var array
    * @access private
    */
    private $results = array();

    /**
    * Конструктор класса. В нем мы указываем максимальное количество потоков и имя сервера. Оба аргумента необязательны.
    *
    * @param int $maxthreads максимальное количество потоков, по умолчанию 10
    * @param string $server имя сервера, по умолчанию имя сервера, на котором запущено приложение
    * @access public
    */
    public function __construct($maxthreads = 10, $server = '')
    {
    if ($server)
    $this->server = $server;
    else
    $this->server = $_SERVER['SERVER_NAME'];

    $this->maxthreads = $maxthreads;
    }

    /**
    * Указываем имя скрипта, который выполняет нужную нам задачу
    *
    * @param string $scriptname имя скрипта, включая путь к нему
    * @access public
    */
    public function setScriptName($scriptname)
    {
    if (!$fp = fopen('http://'.$this->server.'/'.$scriptname, 'r'))
    throw new Exception('Cant open script file');

    fclose($fp);

    $this->scriptname = $scriptname;
    }

    /**
    * Задаем параметры, которые мы будем передавать скрипту
    *
    * @param array $params массив параметров
    * @access public
    */
    public function setParams($params = array())
    {
    $this->params = $params;
    }

    /**
    * Выполняем задачу, комментарии в коде
    *
    * @access public
    */
    public function execute()
    {
    // Запускаем механизм, и он работает, пока не выполнятся все потоки
    do {
    // Если не превысили лимит потоков
    if (count($this->threads) < $maxthreads) {
    // Если удается получить следующий набор параметров
    if ($item = current($this->params)) {

    // Формируем запрос методом GET

    $query_string = '';

    foreach ($item as $key=>$value)
    $query_string .= '&'.urlencode($key).'='.urlencode($value);

    $query = "GET http://".$this->server."/". $this->scriptname."?".$query_string." HTTP/1.0\r\n";

    // Открыватем соединение

    if (!$fsock = fsockopen($this->server, 80))
    throw new Exception('Cant open socket connection');

    fputs($fsock, $query);
    fputs($fsock, "Host: $server\r\n");
    fputs($fsock, "\r\n");

    stream_set_blocking($fsock, 0);
    stream_set_timeout($fsock, 3600);

    // Записываем поток

    $this->threads[] = $fsock;

    // Переходим к следующему элементу

    next($this->params);
    }
    }

    // Перебираем потоки
    foreach ($this->threads as $key=>$value) {
    // Если поток отработал, закрываем и удаляем
    if (feof($value)) {
    fclose($value);
    unset($this->threads[$key]);
    } else {
    // Иначе считываем результаты
    $this->results[] = fgets($value);
    }
    }

    // Можно поставить задержку, чтобы не повесить сервер
    sleep(1);

    // … пока не выполнятся все потоки
    } while (count($this->threads) > 0);

    return $this->results;
    }
    }


    Также можно этот класс скачать, чтобы не копипастить :-).

    Оригинал
  • Trej Gun

    Сообщения: 5305 Репутация: N Группа: в ухо

    Spritz 19 сентября 2008 г. 12:03, спустя 31 минуту 33 секунды

    ошибка
    stream_set_blocking($fsock, O);

    там надо 0 а не О

    while (count($this->threads) > O);

    тожесамое

    А теперь критика
    вобщем ты обращаешься
    if (!$fp = fopen('http://'.$this->server.'/'.$scriptname, 'r'))

    тебе файл открывать не надо, достаточно проверить на существование
    file_exists()


    throw new Exception('Cant open socket connection');

    неправильно потому что при первой же ошибке станет весь скрипт
    имхо лучше continue; + логирование почему ошибка
    если ошибка 5 раз одна и таже тогда можно скрипт остановить

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



    //$data => DB;
    $error = 0;

    $connection = fsockopen ($host, 25, $errno, $errstr, 1);
    foreach ($data as $mail){

    if (!$success){
       sleep(1);
       $error +=1;
       if($error<5)
           continue;
       else
           break;
    }
    $success = 0;

     if(fgets($connection,3) != "220") continue;
     fputs($connection, "HELO $mydomain\n");
     if(fgets($connection,3) != "250") continue;
     fputs($connection, "MAIL FROM: $from\n");
     if(fgets($connection,3) != "250") continue;
     fputs($connection, "RCPT TO: $to\n");
     if(fgets($connection,3) != "250") continue;
     fputs($connection, "DATA\n");
     if(fgets($connection,3) != "354") continue;
     fputs($connection, "To: $to\nFrom: $from\nSubject: $subject\n$headers\n\n$message\n.\n");
     if(fgets($connection,3) != "250") continue;
     fputs($connection,"QUIT\n");
     if(fgets($connection,3) != "221") continue;

    $success = 1;
    }
    fclose ($connection);
  • sap

    Сообщения: 2701 Репутация: N Группа: Кто попало

    Spritz 19 сентября 2008 г. 12:38, спустя 34 минуты 46 секунд

    там надо 0 а не О

    Бля, то я парсером напартачил.

    неправильно потому что при первой же ошибке станет весь скрипт
    имхо лучше continue; + логирование почему ошибка
    если ошибка 5 раз одна и таже тогда можно скрипт остановить

    Тут да.

    если тебе надо быстро послать письма то все делаеться проще

    Мне надо теоретическая реализация многопоточности :)
  • Trej Gun

    Сообщения: 5305 Репутация: N Группа: в ухо

    Spritz 19 сентября 2008 г. 12:46, спустя 7 минут 56 секунд

    Мне надо теоретическая реализация многопоточности :)

    напиши мне многопоточно в файл чтото
  • sap

    Сообщения: 2701 Репутация: N Группа: Кто попало

    Spritz 19 сентября 2008 г. 12:52, спустя 5 минут 40 секунд

    напиши мне многопоточно в файл чтото

    В рандомном порядке — без вопросов :)
  • sap

    Сообщения: 2701 Репутация: N Группа: Кто попало

    Spritz 19 сентября 2008 г. 13:01, спустя 9 минут 45 секунд

    тебе файл открывать не надо, достаточно проверить на существование
    file_exists();

    Не сработает для другого сервера
    file_exists('http://example.com/test.php');
  • Trej Gun

    Сообщения: 5305 Репутация: N Группа: в ухо

    Spritz 19 сентября 2008 г. 14:13, спустя 1 час 12 минут 4 секунды

    может тогда get_headers всеравно быстрее чем fopen
  • sap

    Сообщения: 2701 Репутация: N Группа: Кто попало

    Spritz 19 сентября 2008 г. 14:16, спустя 2 минуты 38 секунд

    Может быть, не знал такой функции.
  • Trej Gun

    Сообщения: 5305 Репутация: N Группа: в ухо

    Spritz 23 сентября 2008 г. 15:08, спустя 4 дня 52 минуты

  • pasha

    Сообщения: 1048 Репутация: N Группа: Адекваты

    Spritz 6 марта 2009 г. 15:40, спустя 164 дня 1 час 31 минуту

    sap, так ли я делаю?

    include_once('multithreading.class.php');

    $data = array('time'=>time());

    $multithreading = new MultiThreading();

    // mailer.php — скрипт, который отправляет письмо по адресу, переданному ему методом GET,
    // то есть [email protected]
    $multithreading->setScriptName('e/1/script.php');

    $multithreading->setParams($data);

    $multithreading->execute();


    script.php


    session_start();

    $_SESSION['session'][] = $_GET['time'];


    но у меня ошибка Warning: Invalid argument supplied for foreach() in Z:\…..\multithreading.class.php on line 116
    foreach ($item as $key=>$value)



    может я вообще не так использую?
  • vlasenkov

    Сообщения: 34 Репутация: N Группа: Кто попало

    Spritz 28 марта 2009 г. 14:44, спустя 21 день 23 часа 4 минуты

    Здравствуйте!
    У меня такой вопрос. Я решил воспользоваться данным эмулятором многопоточности, решил для начало просто протестить, разобраться как это работает, и сталкнулся вот с чем. У меня есть файл который записывает в БД данные. Соответственно запускают скрипт который передает данные для добавления через много поточность. Все очень быстро сработало, но вот непонятная вещь. Данных 100, а записывается намного больше и более того, скрипт закончил работу а данные добавляются (просматриваю через PHPMyAdmin) т.е. это либо его глюк либо еще чего (я в шоке на самом деле), и более того я закрыл все браузеры, посмотрел количество записей и оно увеличилось (я просто в шоке). Скажите в чем может быть проблема и что вам из кода показать, что вам поможет?

    Спасибо!
  • vlasenkov

    Сообщения: 34 Репутация: N Группа: Кто попало

    Spritz 28 марта 2009 г. 14:51, спустя 7 минут 49 секунд

    Это ужас какой-то, я впервый раз с таким сталкиваюсь. Удалил БД, поставил с таким же именем, с тойже таблицой и с темиже стобцами, и началось добавление элементов. Что такое? подскажите пожалуйста.
  • Lirck

    Сообщения: 1658 Репутация: N Группа: Джедаи

    Spritz 28 марта 2009 г. 14:56, спустя 4 минуты 9 секунд

    убей процесс php
  • Lirck

    Сообщения: 1658 Репутация: N Группа: Джедаи

    Spritz 28 марта 2009 г. 14:57, спустя 1 минуту 38 секунд

    перезапусти короче его
  • vlasenkov

    Сообщения: 34 Репутация: N Группа: Кто попало

    Spritz 28 марта 2009 г. 14:59, спустя 1 минуту 23 секунды

    aivee, Спасибо, а что делать с этим скриптом? Или что вообще произошло? Если я на хостинге такое запущу будет же копец!

Пожалуйста, авторизуйтесь, чтобы написать комментарий!