├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Exceptions └── Exception.php ├── LICENSE.md ├── README.md ├── SimpleThread.php ├── Tests └── ThreadTest.php ├── Thread.php ├── ThreadPool.php ├── composer.json ├── docs ├── en │ ├── 0.Index.md │ ├── 1.Main.md │ ├── 2.Events.md │ ├── 3.Options.md │ ├── 4.Hooks.md │ ├── 5.Signals.md │ ├── 6.Other.md │ └── Examples.md └── ru │ ├── 0.Index.md │ ├── 1.Main.md │ ├── 2.Events.md │ ├── 3.Options.md │ ├── 4.Hooks.md │ ├── 5.Signals.md │ ├── 6.Other.md │ └── Examples.md ├── examples ├── example.php └── speed_test.php └── phpunit.xml.dist /.gitignore: -------------------------------------------------------------------------------- 1 | # Composer and packages 2 | composer.phar 3 | composer.lock 4 | vendor/ 5 | 6 | # PhpUnit 7 | phpunit.xml 8 | code_coverage/ 9 | 10 | # Ignore IDE settings 11 | *.iml 12 | *.ipr 13 | *.iws 14 | .idea/ 15 | 16 | # Ignore temp work dirs and files 17 | _/ 18 | *~ 19 | ~* 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.3.3 5 | - 5.3 6 | - 5.4 7 | - 5.5 8 | 9 | before_script: 10 | - composer install 11 | 12 | script: phpunit --configuration phpunit.xml.dist --group=unit 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | CHANGELOG 2 | ========= 3 | 4 | ## Version 1.2 (work in progress) 5 | 6 | 7 | ## Version 1.1 (28.05.2013) 8 | - **MINOR:** Some refactoring, fixes and improvements (amal) 9 | 10 | 26.05.2013 11 | - **MINOR:** `Thread::cleanAll()` method added (amal) 12 | - **IMPROVED:** Better waiting for dead child process with `pcntl_waitpid` (amal) 13 | - **IMPROVED:** The child is now explicitly notify a parent before death (amal) 14 | - **MINOR:** Support for the updated AzaLibEvent API (amal) 15 | - **CHANGED:** Confirmation mode (eventLocking) removed for support for the newest libevent (amal) 16 | 17 | 25.05.2013 18 | - **FEATURE:** Support for the newest libevent has been added (amal) 19 | - **MINOR:** Added test for huge events data (amal) 20 | 21 | 24.05.2013 22 | - **IMPROVED:** Separate detailed documentation in two languages (amal) 23 | 24 | 16.05.2013 25 | - **MINOR:** Small improvements and fixes (amal) 26 | - **IMPROVED:** IPC optimizations (8-10% speedup) (amal) 27 | - **FEATURE:** Simple API for closure thread - `SimpleThread` class (amal) 28 | - **FEATURE:** New `onCleanup` hook (amal) 29 | - **FEATURE:** Thread now can be configured externally (amal) 30 | 31 | 13.05.2013 32 | - **FEATURE:** Threads statistics collection + API for accessing it via pool (Issue #4, amal) 33 | - **MINOR:** Additional tests (empty argument/result, thread hooks) (amal) 34 | 35 | 12.05.2013 36 | - **FIXED:** Inconsistent behaviour with `multitask=false` removed (Issue #6, amal) 37 | 38 | 11.05.2013 39 | - **IMPROVED:** Full code coverage (amal) 40 | - **IMPROVED:** Speedup in IPC. Overall, IPC accelerated by 15-50% compared with the v1.0 release (amal) 41 | - **FIXED:** Job identifier added to discard orphaned results (amal) 42 | - **MINOR:** Small typo fix (amal) 43 | 44 | 09.05.2013 45 | - **MINOR:** Error callbacks for buffer events (amal) 46 | 47 | 05.05.2013 48 | - **FIXED:** Discarding of duplicate job packets. This could occur when the worker dies (amal) 49 | 50 | 04.05.2013 51 | - **FIXED:** Thread can no longer start new job while result of previous is not fetched (amal) 52 | - **MINOR:** More tests for sync mode, better groups of tests (amal) 53 | - **FIXED:** Confirmed bug fix for resources damage with child death (Issue #2, amal) 54 | 55 | 03.05.2013 56 | - **IMPROVED:** Thread pool structure API improvements (amal) 57 | 58 | 28.04.2013 59 | - **CHANGED:** Debug flag moved to the end of arguments list in pool and thread constructors (amal) 60 | 61 | 27.04.2013 62 | - **FIXED:** Master pipe read event cleanup after worker death added (cause can be damaged) (amal) 63 | - **IMPROVED:** New tests, better feature and code coverage (amal) 64 | - **CHANGED:** Main processing errors handling now works without exceptions (amal) 65 | - **FIXED:** Removed incorrect event loop cleanup in some cases (amal) 66 | 67 | 26.04.2013 68 | - **CHANGED:** More thoughtful public getters instead of public/protected properties (amal) 69 | - **MINOR:** Better PhpDocs and some code reorganization (amal) 70 | - **IMPROVED:** Added cleanup for all other (redundant) threads and pools in child after forking (amal) 71 | 72 | 25.04.2013 73 | - **FIXED:** Signal handler in master process with many different threads now works normally (amal) 74 | - **FIXED:** Confirmation mode (eventLocking) for worker events fixed (amal) 75 | - **MINOR:** Debugging improvements. PID update for every call, more data in logs (amal) 76 | 77 | 21.04.2013 78 | - **MINOR:** Many new tests, better feature and code coverage (amal) 79 | - **MINOR:** Many small optimizations, improvements and code cleanup (amal) 80 | - **FEATURE:** Optional arguments mapping (amal) 81 | - **FIXED:** Support for several different threads used at one time not in pool (amal) 82 | - **FIXED:** Worker interval to check parent process did not restarted after first iteration (amal) 83 | - **FIXED:** Worker job waiting timeout missed the first iteration (amal) 84 | - **FIXED:** Tags stripping for debug messages could corrupt them (`strip_tags` replaced with regex) (amal) 85 | 86 | 17.04.2013 87 | - **MINOR:** Better event listeners cleanup (amal) 88 | - **MINOR:** `@return $this` in PhpDocs (amal) 89 | 90 | 14.04.2013 91 | - **FIXED:** Fixed issue with exceptions in events triggering (amal) 92 | 93 | 13.04.2013 94 | - **FIXED:** `Thread::onFork` hook is now properly called in child after forking (Issue #7, amal) 95 | - **MINOR:** Small thread improvements with code cleanup and refactoring (amal) 96 | - **MINOR:** Unit tests structure improvements (amal) 97 | 98 | 09.04.2013 99 | - **IMPROVED:** `Thread::forkThread` method visibility changed to protected (Issue #3, amal) 100 | - **MINOR:** Small code style, comments and tests improvements (amal) 101 | - **FIXED:** Remove dependency on Daemon::getTimeForLog (Issue #1, amal) 102 | 103 | 08.04.2013 104 | - **MINOR:** Massive README and CHANGELOG improvements (amal) 105 | 106 | 107 | ## Version 1.0 (27.02.2013) 108 | - **MINOR:** Refactoring and improvements (amal) 109 | - **FEATURE:** Travis CI support (amal) 110 | - **FEATURE:** Composer support (amal) 111 | 112 | 113 | ## Version 0.9 (01.08.2012) 114 | - **FIXED:** `pool->getState` visibility fix (protected => public) (amal) 115 | 116 | 10.04.12 117 | - **FIXED:** 4096 bytes results limit (Issue amal/AzaThread#5, amal) 118 | - **FIXED:** Fixed results error with SIGCHLD (Issue amal/AzaThread#4, amal) 119 | - **CHANGED:** Massive component rewrite. Namespaces and PSR-0. (amal) 120 | 121 | 122 | ## Version 0.1 (25.01.2012) 123 | 21.12.11 124 | - **FEATURE:** Thread events with pool (amal) 125 | 126 | 13.12.11 127 | - First public release 128 | -------------------------------------------------------------------------------- /Exceptions/Exception.php: -------------------------------------------------------------------------------- 1 | 12 | * @license MIT 13 | */ 14 | class Exception extends BaseExeption {} 15 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2013 Amal Samally and AzaGroup 4 | https://github.com/Anizoptera/AzaThread 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is furnished 11 | to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AzaThread 2 | ========= 3 | 4 | Simple and powerful threads emulation component for PHP (based on forks). 5 | Old name - CThread. 6 | 7 | https://github.com/Anizoptera/AzaThread 8 | 9 | [![Build Status][TravisImage]][Travis] 10 | 11 | 12 | Table of Contents 13 | ----------------- 14 | 15 | 1. [Introduction](#introduction) 16 | 2. [Requirements](#requirements) 17 | 3. [Installation](#installation) 18 | 4. [Documentation and examples](#documentation-and-examples) 19 | 5. [Tests](#tests) 20 | 6. [Credits](#credits) 21 | 7. [License](#license) 22 | 8. [Links](#links) 23 | 24 | 25 | Introduction 26 | ------------ 27 | 28 | **Features:** 29 | 30 | * Uses [forks](http://php.net/pcntl-fork) to operate asynchronously; 31 | * Supports synchronous compatibility mode if there are no required extensions; 32 | * Reuse of the child processes; 33 | * Full exchange of data between processes. Sending arguments, receiving results; 34 | * Transfer of events between the "thread" and the parent process; 35 | * Working with a thread pool with preservation of multiple use, passing arguments and receiving results; 36 | * Uses [libevent][] with socket pairs for efficient inter-process communication; 37 | * Supports two variants of data serialization for transfer (igbinary, native php serialization); 38 | * Errors handling; 39 | * Timeouts for work, child process waiting, initialization; 40 | * Maximum performance and customization; 41 | 42 | 43 | Requirements 44 | ------------ 45 | 46 | * PHP 5.3.3 (or later); 47 | * Unix system; 48 | * [libevent][]; 49 | * [pcntl](http://php.net/pcntl); 50 | * [posix](http://php.net/posix); 51 | * [AzaLibevent](https://github.com/Anizoptera/AzaLibEvent) - will be installed automatically with composer; 52 | * [AzaSocket](https://github.com/Anizoptera/AzaSocket) - will be installed automatically with composer; 53 | * [AzaCliBase](https://github.com/Anizoptera/AzaCliBase) - will be installed automatically with composer; 54 | 55 | NOTE: You can use synchronous compatibility mode even without requirements (or on windows, for example). 56 | 57 | 58 | Installation 59 | ------------ 60 | 61 | The recommended way to install AzaThread is [through composer](http://getcomposer.org). 62 | You can see [package information on Packagist][ComposerPackage]. 63 | 64 | ```JSON 65 | { 66 | "require": { 67 | "aza/thread": "~1.0" 68 | } 69 | } 70 | ``` 71 | 72 | 73 | Documentation and examples 74 | -------------------------- 75 | 76 | See [full documentation](docs/en/0.Index.md) and [main examples](docs/en/Examples.md). Documentation is available in several languages​​! 77 | 78 | Other examples can be seen in the file [examples/example.php](examples/example.php) and in unit test [Tests/ThreadTest.php](Tests/ThreadTest.php). 79 | 80 | You can also run the performance tests, choose the number of threads and pick the best settings for your system configuration by using [examples/speed_test.php](examples/speed_test.php). 81 | 82 | 83 | Tests 84 | ----- 85 | 86 | Tests are in the `Tests` folder. 87 | To run them, you need PHPUnit. 88 | Example: 89 | 90 | $ phpunit --configuration phpunit.xml.dist 91 | 92 | 93 | Credits 94 | ------- 95 | 96 | AzaThread is a part of [Anizoptera CMF][], written by [Amal Samally][] (amal.samally at gmail.com) and [AzaGroup][] team. 97 | 98 | 99 | License 100 | ------- 101 | 102 | Released under the [MIT](LICENSE.md) license. 103 | 104 | 105 | Links 106 | ----- 107 | 108 | * [Mail list](mailto:azathread@googlegroups.com) (via [Google Group](https://groups.google.com/forum/#!forum/azathread)) 109 | * [Composer package][ComposerPackage] 110 | * [Last build on the Travis CI][Travis] 111 | * [Project profile on the Ohloh](https://www.ohloh.net/p/AzaThread) 112 | * (RU) [AzaThread — многопоточность для PHP с блэкджеком](http://habrahabr.ru/blogs/php/134501/) 113 | * Other Anizoptera CMF components on the [GitHub][Anizoptera CMF] / [Packagist](https://packagist.org/packages/aza) 114 | * (RU) [AzaGroup team blog][AzaGroup] 115 | 116 | 117 | 118 | [libevent]: http://php.net/libevent 119 | 120 | [Anizoptera CMF]: https://github.com/Anizoptera 121 | [Amal Samally]: http://azagroup.ru/about/#amal 122 | [AzaGroup]: http://azagroup.ru/ 123 | [ComposerPackage]: https://packagist.org/packages/aza/thread 124 | [TravisImage]: https://secure.travis-ci.org/Anizoptera/AzaThread.png?branch=master 125 | [Travis]: http://travis-ci.org/Anizoptera/AzaThread 126 | -------------------------------------------------------------------------------- /SimpleThread.php: -------------------------------------------------------------------------------- 1 | 12 | * @license MIT 13 | */ 14 | class SimpleThread extends Thread 15 | { 16 | /** 17 | * Whether the thread will wait for next tasks. 18 | * Preforked threads are always multitask. 19 | * 20 | * @see prefork 21 | */ 22 | protected $multitask = false; 23 | 24 | /** 25 | * Perform pre-fork, to avoid wasting resources later. 26 | * Preforked threads are always multitask. 27 | * 28 | * @see multitask 29 | */ 30 | protected $prefork = false; 31 | 32 | /** 33 | * Maximum timeout for master to wait for the job results 34 | * (in seconds, can be fractional). 35 | * Set it to less than one, to disable. 36 | */ 37 | protected $timeoutMasterResultWait = 10; 38 | 39 | 40 | /** 41 | * Callable 42 | * 43 | * @var callable 44 | */ 45 | protected $callable; 46 | 47 | 48 | 49 | /** 50 | * Creates new thread with closure 51 | * 52 | * @param callable|Closure $callable

53 | * Thread closure (callable) 54 | *

55 | * @param array $options [optional]

56 | * Thread options (array [property => value]) 57 | *

58 | * @param bool $debug [optional]

59 | * Whether to output debugging information 60 | *

61 | * 62 | * @return static 63 | */ 64 | public static function create($callable, array $options = null, $debug = false) 65 | { 66 | 67 | $options || $options = array(); 68 | $options['callable'] = $callable; 69 | return new static(null, null, $debug, $options); 70 | } 71 | 72 | 73 | /** 74 | * Prepares closure (only in PHP >= 5.4.0) 75 | * 76 | * @codeCoverageIgnore 77 | */ 78 | protected function onLoad() 79 | { 80 | // Prepare closure 81 | $callable = $this->callable; 82 | if ($callable instanceof Closure && method_exists($callable, 'bindTo')) { 83 | /** @noinspection PhpUndefinedMethodInspection */ 84 | $callable->bindTo($this, $this); 85 | } 86 | } 87 | 88 | /** 89 | * Callable cleanup 90 | */ 91 | protected function onCleanup() 92 | { 93 | $this->callable = null; 94 | } 95 | 96 | 97 | /** 98 | * Main processing. You need to override this method. 99 | * Use {@link getParam} method to get processing parameters. 100 | * Returned result will be available via {@link getResult} 101 | * in the master process. 102 | * 103 | * @internal 104 | */ 105 | function process() 106 | { 107 | return call_user_func_array( 108 | $this->callable, $this->getParams() 109 | ); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Tests/ThreadTest.php: -------------------------------------------------------------------------------- 1 | 23 | * @license MIT 24 | */ 25 | class ThreadTest extends TestCase 26 | { 27 | /** @var bool */ 28 | protected static $defUseForks; 29 | 30 | /** @var int */ 31 | protected static $defIpcDataMode; 32 | 33 | 34 | /** 35 | * {@inheritdoc} 36 | */ 37 | public static function setUpBeforeClass() 38 | { 39 | self::$defUseForks = Thread::$useForks; 40 | self::$defIpcDataMode = Thread::$ipcDataMode; 41 | } 42 | 43 | /** 44 | * {@inheritdoc} 45 | */ 46 | protected function setUp() 47 | { 48 | // Set values to default 49 | Thread::$useForks = self::$defUseForks; 50 | Thread::$ipcDataMode = self::$defIpcDataMode; 51 | 52 | // Cleanup 53 | EventBase::cleanAllLoops(); 54 | Thread::cleanAll(); 55 | gc_collect_cycles(); 56 | } 57 | 58 | /** 59 | * {@inheritdoc} 60 | */ 61 | public static function tearDownAfterClass() 62 | { 63 | // Set values to default 64 | Thread::$useForks = self::$defUseForks; 65 | Thread::$ipcDataMode = self::$defIpcDataMode; 66 | 67 | // Cleanup 68 | EventBase::cleanAllLoops(); 69 | Thread::cleanAll(); 70 | gc_collect_cycles(); 71 | } 72 | 73 | 74 | 75 | /** 76 | * Tests threads debugging 77 | * 78 | * @author amal 79 | * @group unit 80 | */ 81 | public function testDebug() 82 | { 83 | $this->expectOutputString(''); 84 | 85 | Thread::$useForks = false; 86 | 87 | $value = 'test12345'; 88 | 89 | // Test thread debug output 90 | $thread = new TestThreadReturnFirstArgument(); 91 | $this->assertSame(Thread::STATE_WAIT, $thread->getState()); 92 | $ref = new ReflectionMethod($thread, 'debug'); 93 | $ref->setAccessible(true); 94 | ob_start(); ob_start(); 95 | $thread->debug = true; 96 | $ref->invoke($thread, $value); 97 | $thread->debug = false; 98 | ob_get_clean(); 99 | $output = ob_get_clean(); 100 | $this->assertContains($value, $output); 101 | $thread->cleanup(); 102 | 103 | // Test thread pool debug output 104 | $className = get_class($thread); 105 | ob_start(); ob_start(); 106 | $pool = new ThreadPool($className); 107 | $ref = new ReflectionMethod($pool, 'debug'); 108 | $ref->setAccessible(true); 109 | $pool->debug = true; 110 | $ref->invoke($pool, $value); 111 | $pool->debug = false; 112 | ob_get_clean(); 113 | $output = ob_get_clean(); 114 | $this->assertContains($value, $output); 115 | $pool->cleanup(); 116 | } 117 | 118 | /** 119 | * Tests external configured thread 120 | * 121 | * @author amal 122 | * @group unit 123 | */ 124 | public function testExternalConfiguration() 125 | { 126 | Thread::$useForks = false; 127 | 128 | $property = 'timeoutWorkerJobWait'; 129 | $expected = -mt_rand(100, 999); 130 | 131 | $thread = new TestThreadReturnFirstArgument( 132 | null, null, false, array($property => $expected) 133 | ); 134 | $ref = new ReflectionProperty($thread, $property); 135 | $ref->setAccessible(true); 136 | $value = $ref->getValue($thread); 137 | $this->assertSame($expected, $value); 138 | 139 | $thread = new TestThreadReturnFirstArgument(); 140 | $value = $ref->getValue($thread); 141 | $this->assertNotSame($expected, $value); 142 | } 143 | 144 | 145 | 146 | #region Synchronous (fallback mode) tests 147 | 148 | /** 149 | * Simple thread test (sync mode) 150 | * 151 | * @author amal 152 | * @group unit 153 | */ 154 | public function testSyncThread() 155 | { 156 | Thread::$useForks = false; 157 | $this->processThread(false); 158 | } 159 | 160 | /** 161 | * Additional thread test 162 | * 163 | * @author amal 164 | * @group unit 165 | */ 166 | public function testSyncThread2() 167 | { 168 | Thread::$useForks = false; 169 | $this->processThread2(false); 170 | } 171 | 172 | /** 173 | * Thread test with big data (sync mode) 174 | * 175 | * @author amal 176 | * @group unit 177 | */ 178 | public function testSyncThreadWithBigData() 179 | { 180 | Thread::$useForks = false; 181 | $this->processThread(false, true); 182 | } 183 | 184 | /** 185 | * Thread test with childs (sync mode) 186 | * 187 | * @author amal 188 | * @group unit 189 | */ 190 | public function testSyncThreadWithChilds() 191 | { 192 | Thread::$useForks = false; 193 | $this->processThread(false, false, true); 194 | } 195 | 196 | /** 197 | * Thread test with childs and big data (sync mode) 198 | * 199 | * @author amal 200 | * @group unit 201 | */ 202 | public function testSyncThreadWithBigDataAndChilds() 203 | { 204 | Thread::$useForks = false; 205 | $this->processThread(false, true, true); 206 | } 207 | 208 | /** 209 | * Thread events test (sync mode) 210 | * 211 | * @author amal 212 | * @group unit 213 | */ 214 | public function testSyncThreadWithEvents() 215 | { 216 | $debug = false; 217 | 218 | Thread::$useForks = false; 219 | $this->processThreadEvent($debug); 220 | 221 | 222 | // Non-closure test 223 | $thread = new TestThreadEvents(null, null, $debug); 224 | 225 | $thread->bind( 226 | TestThreadEvents::EV_PROCESS, 227 | array($this, 'processEventCallback') 228 | ); 229 | 230 | $count = $this->getCount(); 231 | $thread->run(1)->wait(); 232 | 233 | $this->assertSame($count+3, $this->getCount()); 234 | $this->assertSame(Thread::STATE_WAIT, $thread->getState()); 235 | $this->assertTrue( 236 | $thread->getSuccess(), 237 | 'Job failure - #' . $thread->getLastErrorCode() 238 | . ' ' . $thread->getLastErrorMsg() 239 | ); 240 | 241 | $thread->cleanup(); 242 | } 243 | 244 | /** 245 | * Thread events test with big data (sync mode) 246 | * 247 | * @author amal 248 | * @group unit 249 | */ 250 | public function testSyncThreadWithEventsBigData() 251 | { 252 | Thread::$useForks = false; 253 | $this->processThreadEvent(false, true); 254 | } 255 | 256 | /** 257 | * Errorable thread test (sync mode) 258 | * 259 | * @author amal 260 | * @group unit 261 | */ 262 | public function testSyncThreadErrorable() 263 | { 264 | Thread::$useForks = false; 265 | $this->processThreadErrorable(false); 266 | } 267 | 268 | /** 269 | * Thread arguments mapping test (sync mode) 270 | * 271 | * @author amal 272 | * @group unit 273 | */ 274 | public function testSyncThreadArgumentsMapping() 275 | { 276 | Thread::$useForks = false; 277 | $this->processThreadArgumentsMapping(false); 278 | } 279 | 280 | /** 281 | * Multitask disabled thread test (sync mode) 282 | * 283 | * @author amal 284 | * @group unit 285 | * @ticket GitHub Anizoptera/AzaThread/#6 286 | */ 287 | public function testSyncThreadOnetask() 288 | { 289 | Thread::$useForks = false; 290 | $this->processThread(false, true, false, true); 291 | } 292 | 293 | /** 294 | * Closure thread test (sync mode) 295 | * 296 | * @author amal 297 | * @group unit 298 | */ 299 | public function testSyncThreadClosure() 300 | { 301 | Thread::$useForks = false; 302 | $this->processThreadClosure(false); 303 | } 304 | 305 | 306 | /** 307 | * Simple thread pool test (sync mode) 308 | * 309 | * @author amal 310 | * @group unit 311 | */ 312 | public function testSyncThreadPool() 313 | { 314 | Thread::$useForks = false; 315 | $this->processPool(false); 316 | } 317 | 318 | /** 319 | * Additional thread pool test (sync mode) 320 | * 321 | * @author amal 322 | * @group unit 323 | */ 324 | public function testSyncThreadPool2() 325 | { 326 | Thread::$useForks = false; 327 | $this->processPool2(false); 328 | } 329 | 330 | /** 331 | * Thread pool test with big data (sync mode) 332 | * 333 | * @author amal 334 | * @group unit 335 | */ 336 | public function testSyncThreadPoolWithBigData() 337 | { 338 | Thread::$useForks = false; 339 | $this->processPool(false, true); 340 | } 341 | 342 | /** 343 | * Thread pool test with childs (sync mode) 344 | * 345 | * @author amal 346 | * @group unit 347 | */ 348 | public function testSyncThreadPoolWithChilds() 349 | { 350 | Thread::$useForks = false; 351 | $this->processPool(false, false, true); 352 | } 353 | 354 | /** 355 | * Thread pool test with childs and big data (sync mode) 356 | * 357 | * @author amal 358 | * @group unit 359 | */ 360 | public function testSyncThreadPoolWithBigDataAndChilds() 361 | { 362 | Thread::$useForks = false; 363 | $this->processPool(false, true, true); 364 | } 365 | 366 | /** 367 | * Thread pool events test (sync mode) 368 | * 369 | * @author amal 370 | * @group unit 371 | */ 372 | public function testSyncThreadPoolWithEvents() 373 | { 374 | $debug = false; 375 | 376 | Thread::$useForks = false; 377 | $this->processPoolEvent($debug); 378 | 379 | 380 | // Non-closure test 381 | $pool = new ThreadPool( 382 | __NAMESPACE__ . '\TestThreadEvents', 383 | 1, null, null, $debug 384 | ); 385 | 386 | $pool->bind( 387 | TestThreadEvents::EV_PROCESS, 388 | array($this, 'processEventCallback') 389 | ); 390 | 391 | $threadId = $pool->run(1); 392 | $this->assertNotEmpty($threadId); 393 | 394 | $pool->wait(); 395 | 396 | $pool->cleanup(); 397 | } 398 | 399 | /** 400 | * Errorable thread pool test (sync mode) 401 | * 402 | * @author amal 403 | * @group unit 404 | */ 405 | public function testSyncThreadPoolErrorable() 406 | { 407 | Thread::$useForks = false; 408 | $this->processPoolErrorable(false); 409 | } 410 | 411 | /** 412 | * Thread pool test with external stop (sync mode) 413 | * 414 | * @author amal 415 | * @group unit 416 | */ 417 | public function testSyncThreadPoolErrorableExternal() 418 | { 419 | Thread::$useForks = false; 420 | $this->processPoolErrorableExternal(false); 421 | } 422 | 423 | /** 424 | * Thread pool test with multitask disabled (sync mode) 425 | * 426 | * @author amal 427 | * @group unit 428 | * @ticket GitHub Anizoptera/AzaThread/#6 429 | */ 430 | public function testSyncPoolOnetask() 431 | { 432 | Thread::$useForks = false; 433 | $this->processPool(false, false, true, true); 434 | } 435 | 436 | 437 | /** 438 | * Test for exception in events triggering (sync mode) 439 | * 440 | * @author amal 441 | * @group unit 442 | */ 443 | public function testSyncExceptionInEventListener() 444 | { 445 | Thread::$useForks = false; 446 | $this->processExceptionInEventListener(false); 447 | } 448 | 449 | /** 450 | * Two different threads test (sync mode) 451 | * 452 | * @author amal 453 | * @group unit 454 | */ 455 | public function testSyncTwoDifferentThreads() 456 | { 457 | Thread::$useForks = false; 458 | $this->processTwoDifferentThreads(false); 459 | } 460 | 461 | #endregion 462 | 463 | 464 | #region Full feature tests 465 | 466 | /** 467 | * Simple thread test 468 | * 469 | * @author amal 470 | * @group integrational 471 | * @group thread 472 | * 473 | * @requires extension posix 474 | * @requires extension pcntl 475 | */ 476 | public function testThread() 477 | { 478 | $testCase = $this; 479 | $this->processAsyncTest(function() use ($testCase) { 480 | $testCase->processThread(false); 481 | }, true); 482 | } 483 | 484 | /** 485 | * Additional thread test 486 | * 487 | * @author amal 488 | * @group integrational 489 | * @group thread 490 | * 491 | * @requires extension posix 492 | * @requires extension pcntl 493 | */ 494 | public function testThread2() 495 | { 496 | $testCase = $this; 497 | $this->processAsyncTest(function() use ($testCase) { 498 | $testCase->processThread2(false); 499 | }, true); 500 | } 501 | 502 | /** 503 | * Thread test with big data 504 | * 505 | * @author amal 506 | * @group integrational 507 | * @group thread 508 | * 509 | * @requires extension posix 510 | * @requires extension pcntl 511 | */ 512 | public function testThreadWithBigData() 513 | { 514 | $testCase = $this; 515 | $this->processAsyncTest(function() use ($testCase) { 516 | $testCase->processThread(false, true); 517 | }, true); 518 | } 519 | 520 | /** 521 | * Thread test with childs 522 | * 523 | * @author amal 524 | * @group integrational 525 | * @group thread 526 | * 527 | * @requires extension posix 528 | * @requires extension pcntl 529 | */ 530 | public function testThreadWithChilds() 531 | { 532 | $testCase = $this; 533 | $this->processAsyncTest(function() use ($testCase) { 534 | $testCase->processThread(false, false, true); 535 | }, true); 536 | } 537 | 538 | /** 539 | * Thread test with childs and big data 540 | * 541 | * @author amal 542 | * @group integrational 543 | * @group thread 544 | * 545 | * @requires extension posix 546 | * @requires extension pcntl 547 | */ 548 | public function testThreadWithBigDataAndChilds() 549 | { 550 | $testCase = $this; 551 | $this->processAsyncTest(function() use ($testCase) { 552 | $testCase->processThread(false, true, true); 553 | }); 554 | } 555 | 556 | /** 557 | * Thread events test 558 | * 559 | * @author amal 560 | * @group integrational 561 | * @group thread 562 | * 563 | * @requires extension posix 564 | * @requires extension pcntl 565 | */ 566 | public function testThreadWithEvents() 567 | { 568 | $testCase = $this; 569 | $this->processAsyncTest(function() use ($testCase) { 570 | $testCase->processThreadEvent(false); 571 | }, true); 572 | } 573 | 574 | /** 575 | * Thread events test with big data 576 | * 577 | * @author amal 578 | * @group integrational 579 | * @group thread 580 | * 581 | * @requires extension posix 582 | * @requires extension pcntl 583 | */ 584 | public function testThreadWithEventsBigData() 585 | { 586 | $testCase = $this; 587 | $this->processAsyncTest(function() use ($testCase) { 588 | $testCase->processThreadEvent(false, true); 589 | }, true); 590 | } 591 | 592 | /** 593 | * Errorable thread test 594 | * 595 | * @author amal 596 | * @group integrational 597 | * @group thread 598 | * 599 | * @requires extension posix 600 | * @requires extension pcntl 601 | */ 602 | public function testThreadErrorable() 603 | { 604 | $testCase = $this; 605 | $this->processAsyncTest(function() use ($testCase) { 606 | $testCase->processThreadErrorable(false); 607 | }, true); 608 | } 609 | 610 | /** 611 | * Thread arguments mapping test (sync mode) 612 | * 613 | * @author amal 614 | * @group integrational 615 | * @group thread 616 | * 617 | * @requires extension posix 618 | * @requires extension pcntl 619 | */ 620 | public function testThreadArgumentsMapping() 621 | { 622 | $testCase = $this; 623 | $this->processAsyncTest(function() use ($testCase) { 624 | $testCase->processThreadArgumentsMapping(false); 625 | }, true); 626 | } 627 | 628 | /** 629 | * Multitask disabled thread test 630 | * 631 | * @author amal 632 | * @group integrational 633 | * @group thread 634 | * @ticket GitHub Anizoptera/AzaThread/#6 635 | * 636 | * @requires extension posix 637 | * @requires extension pcntl 638 | */ 639 | public function testThreadOnetask() 640 | { 641 | $debug = false; 642 | $testCase = $this; 643 | $this->processAsyncTest(function() use ($testCase, $debug) { 644 | $testCase->processThread($debug, true, false, true); 645 | $testCase->processThread($debug, false, false, true); 646 | }, true); 647 | } 648 | 649 | /** 650 | * Closure thread test 651 | * 652 | * @author amal 653 | * @group integrational 654 | * @group thread 655 | * 656 | * @requires extension posix 657 | * @requires extension pcntl 658 | */ 659 | public function testThreadClosure() 660 | { 661 | $testCase = $this; 662 | $this->processAsyncTest(function() use ($testCase) { 663 | $testCase->processThreadClosure(false); 664 | }, true); 665 | } 666 | 667 | 668 | /** 669 | * Simple thread pool test 670 | * 671 | * @author amal 672 | * @group integrational 673 | * @group pool 674 | * 675 | * @requires extension posix 676 | * @requires extension pcntl 677 | */ 678 | public function testThreadPool() 679 | { 680 | $testCase = $this; 681 | $this->processAsyncTest(function() use ($testCase) { 682 | $testCase->processPool(false); 683 | }, true); 684 | } 685 | 686 | /** 687 | * Additional thread pool test 688 | * 689 | * @author amal 690 | * @group integrational 691 | * @group pool 692 | * 693 | * @requires extension posix 694 | * @requires extension pcntl 695 | */ 696 | public function testThreadPool2() 697 | { 698 | $testCase = $this; 699 | $this->processAsyncTest(function() use ($testCase) { 700 | $testCase->processPool2(false); 701 | }, true); 702 | } 703 | 704 | /** 705 | * Thread pool test with big data 706 | * 707 | * @author amal 708 | * @group integrational 709 | * @group pool 710 | * 711 | * @requires extension posix 712 | * @requires extension pcntl 713 | */ 714 | public function testThreadPoolWithBigData() 715 | { 716 | $testCase = $this; 717 | $this->processAsyncTest(function() use ($testCase) { 718 | $testCase->processPool(false, true); 719 | }, true); 720 | } 721 | 722 | /** 723 | * Thread pool test with childs 724 | * 725 | * @author amal 726 | * @group integrational 727 | * @group pool 728 | * 729 | * @requires extension posix 730 | * @requires extension pcntl 731 | */ 732 | public function testThreadPoolWithChilds() 733 | { 734 | $testCase = $this; 735 | $this->processAsyncTest(function() use ($testCase) { 736 | $testCase->processPool(false, false, true); 737 | }, true); 738 | } 739 | 740 | /** 741 | * Thread pool test with childs and big data 742 | * 743 | * @author amal 744 | * @group integrational 745 | * @group pool 746 | * 747 | * @requires extension posix 748 | * @requires extension pcntl 749 | */ 750 | public function testThreadPoolWithBigDataAndChilds() 751 | { 752 | $testCase = $this; 753 | $this->processAsyncTest(function() use ($testCase) { 754 | $testCase->processPool(false, true, true); 755 | }); 756 | } 757 | 758 | /** 759 | * Thread pool events test 760 | * 761 | * @author amal 762 | * @group integrational 763 | * @group pool 764 | * 765 | * @requires extension posix 766 | * @requires extension pcntl 767 | */ 768 | public function testThreadPoolWithEvents() 769 | { 770 | $testCase = $this; 771 | $this->processAsyncTest(function() use ($testCase) { 772 | $testCase->processPoolEvent(false); 773 | }); 774 | } 775 | 776 | /** 777 | * Errorable thread pool test 778 | * 779 | * @author amal 780 | * @group integrational 781 | * @group pool 782 | * 783 | * @requires extension posix 784 | * @requires extension pcntl 785 | */ 786 | public function testThreadPoolErrorable() 787 | { 788 | $testCase = $this; 789 | $this->processAsyncTest(function() use ($testCase) { 790 | $testCase->processPoolErrorable(false); 791 | }); 792 | } 793 | 794 | /** 795 | * Thread pool test with external stop (for issue) 796 | * 797 | * @author amal 798 | * @group integrational 799 | * @group pool 800 | * @ticket GitHub Anizoptera/AzaThread/#2 801 | * 802 | * @requires extension posix 803 | * @requires extension pcntl 804 | */ 805 | public function testThreadPoolErrorableExternal() 806 | { 807 | $testCase = $this; 808 | $this->processAsyncTest(function() use ($testCase) { 809 | $testCase->processPoolErrorableExternal(false); 810 | }); 811 | } 812 | 813 | /** 814 | * Thread pool test with multitask disabled 815 | * 816 | * @author amal 817 | * @group integrational 818 | * @group pool 819 | * @ticket GitHub Anizoptera/AzaThread/#6 820 | * 821 | * @requires extension posix 822 | * @requires extension pcntl 823 | */ 824 | public function testPoolOnetask() 825 | { 826 | $testCase = $this; 827 | $this->processAsyncTest(function() use ($testCase) { 828 | $testCase->processPool(false, false, true, true); 829 | }, true); 830 | } 831 | 832 | 833 | /** 834 | * Test for exception in events triggering 835 | * 836 | * @author amal 837 | * @group integrational 838 | * @group thread 839 | * 840 | * @requires extension posix 841 | * @requires extension pcntl 842 | */ 843 | public function testExceptionInEventListener() 844 | { 845 | $testCase = $this; 846 | $this->processAsyncTest(function() use ($testCase) { 847 | $testCase->processExceptionInEventListener(false); 848 | }, true); 849 | } 850 | 851 | /** 852 | * Two different threads test 853 | * 854 | * @author amal 855 | * @group integrational 856 | * @group thread 857 | * 858 | * @requires extension posix 859 | * @requires extension pcntl 860 | */ 861 | public function testTwoDifferentThreads() 862 | { 863 | $testCase = $this; 864 | $this->processAsyncTest(function() use ($testCase) { 865 | $testCase->processTwoDifferentThreads(false); 866 | }); 867 | } 868 | 869 | /** 870 | * POSIX signals handling test 871 | * 872 | * @author amal 873 | * @group integrational 874 | * @group thread 875 | * 876 | * @requires extension posix 877 | * @requires extension pcntl 878 | */ 879 | public function testSignalsHandling() 880 | { 881 | $debug = false; 882 | $testCase = $this; 883 | $this->processAsyncTest(function() use ($testCase, $debug) { 884 | if ($debug) { 885 | echo '----------------------------', PHP_EOL, 886 | "POSIX signals handling test ", PHP_EOL, 887 | '----------------------------', PHP_EOL; 888 | } 889 | 890 | 891 | // Random thread init, to test handling of signals for different threads 892 | $t = new TestThreadReturnFirstArgument( 893 | null, null, $debug 894 | ); 895 | 896 | 897 | TestSignalsHandling::$catchedSignalsInParent = $counter = 0; 898 | $testCase->assertSame($counter, TestSignalsHandling::$catchedSignalsInParent); 899 | 900 | 901 | $thread = new TestSignalsHandling('SignalsHandling', null, $debug); 902 | $thread->wait(); 903 | 904 | $testCase->assertSame($counter++, TestSignalsHandling::$catchedSignalsInParent); 905 | 906 | $catchedSignals = (int)$thread->run()->wait()->getResult(); 907 | $testCase->assertTrue(0 === $catchedSignals); 908 | $thread->getEventLoop()->loop(EVLOOP_NONBLOCK); // Needed sometimes if parent isn't catched signal yet 909 | $testCase->assertSame($counter++, TestSignalsHandling::$catchedSignalsInParent); 910 | 911 | $thread->sendSignalToChild(SIGUSR1); 912 | usleep(1000); 913 | $catchedSignals = (int)$thread->run()->wait()->getResult(); 914 | $testCase->assertTrue(1 === $catchedSignals || 0 === $catchedSignals); 915 | $thread->getEventLoop()->loop(EVLOOP_NONBLOCK); // Needed sometimes if parent isn't catched signal yet 916 | $testCase->assertSame($counter++, TestSignalsHandling::$catchedSignalsInParent); 917 | 918 | $thread->sendSignalToChild(SIGUSR1); 919 | usleep(1000); 920 | $catchedSignals = (int)$thread->run()->wait()->getResult(); 921 | $testCase->assertTrue(2 === $catchedSignals || 1 === $catchedSignals); 922 | $thread->getEventLoop()->loop(EVLOOP_NONBLOCK); // Needed sometimes if parent isn't catched signal yet 923 | $testCase->assertSame($counter++, TestSignalsHandling::$catchedSignalsInParent); 924 | 925 | $thread->sendSignalToChild(SIGUSR1); 926 | usleep(1000); 927 | $catchedSignals = (int)$thread->run()->wait()->getResult(); 928 | $testCase->assertTrue(3 === $catchedSignals || 2 === $catchedSignals); 929 | $thread->getEventLoop()->loop(EVLOOP_NONBLOCK); // Needed sometimes if parent isn't catched signal yet 930 | $testCase->assertSame($counter++, TestSignalsHandling::$catchedSignalsInParent); 931 | 932 | $thread->sendSignalToParent(SIGWINCH); 933 | $thread->sendSignalToParent(SIGCHLD); 934 | 935 | $thread->getEventLoop()->loop(EVLOOP_NONBLOCK); 936 | $thread->cleanup(); 937 | 938 | 939 | $thread = new TestSignalsHandling(null, null, $debug); 940 | $thread->wait()->sendSignalToChild(SIGUSR1); 941 | usleep(1000); 942 | $catchedSignals = (int)$thread->run()->wait()->getResult(); 943 | $testCase->assertTrue(1 === $catchedSignals || 0 === $catchedSignals); 944 | $thread->getEventLoop()->loop(EVLOOP_NONBLOCK); // Needed sometimes if parent isn't catched signal yet 945 | $testCase->assertSame($counter++, TestSignalsHandling::$catchedSignalsInParent); 946 | $thread->getEventLoop()->loop(EVLOOP_NONBLOCK); 947 | $thread->cleanup(); 948 | 949 | 950 | $thread = new TestSignalsHandling(null, null, $debug); 951 | $thread->sendSignalToChild(SIGUSR1)->wait(); 952 | usleep(1000); 953 | $catchedSignals = (int)$thread->run()->wait()->getResult(); 954 | $testCase->assertTrue(0 === $catchedSignals || 1 === $catchedSignals); 955 | $thread->getEventLoop()->loop(EVLOOP_NONBLOCK); // Needed sometimes if parent isn't catched signal yet 956 | $testCase->assertSame($counter++, TestSignalsHandling::$catchedSignalsInParent); 957 | $thread->getEventLoop()->loop(EVLOOP_NONBLOCK); 958 | $thread->cleanup(); 959 | 960 | 961 | $t->cleanup(); 962 | unset($counter); 963 | }); 964 | } 965 | 966 | 967 | /** 968 | * Prefork wait timeout test 969 | * 970 | * @author amal 971 | * @group integrational 972 | * @group timeout 973 | * 974 | * @requires extension posix 975 | * @requires extension pcntl 976 | */ 977 | public function testPreforkWaitTimeout() 978 | { 979 | $debug = false; 980 | $testCase = $this; 981 | $this->processAsyncTest(function() use ($testCase, $debug) { 982 | if ($debug) { 983 | echo '-------------------------', PHP_EOL, 984 | "Prefork wait timeout test ", PHP_EOL, 985 | '-------------------------', PHP_EOL; 986 | } 987 | 988 | $thread = new TestPreforkWaitTimeout( 989 | null, null, $debug 990 | ); 991 | 992 | $testCase->assertFalse($thread->getSuccess()); 993 | $testCase->assertSame( 994 | Thread::ERR_TIMEOUT_INIT, 995 | $thread->getLastErrorCode() 996 | ); 997 | $testCase->assertContains( 998 | 'Exceeded timeout: thread initialization', 999 | $thread->getLastErrorMsg() 1000 | ); 1001 | $testCase->assertSame( 1002 | Thread::STATE_WAIT, 1003 | $thread->getState() 1004 | ); 1005 | 1006 | $thread->cleanup(); 1007 | }, true); 1008 | } 1009 | 1010 | /** 1011 | * Result wait timeout test 1012 | * 1013 | * @author amal 1014 | * @group integrational 1015 | * @group timeout 1016 | * 1017 | * @requires extension posix 1018 | * @requires extension pcntl 1019 | */ 1020 | public function testResultWaitTimeout() 1021 | { 1022 | $debug = false; 1023 | $testCase = $this; 1024 | $this->processAsyncTest(function() use ($testCase, $debug) { 1025 | if ($debug) { 1026 | echo '-------------------------', PHP_EOL, 1027 | "Result wait timeout test ", PHP_EOL, 1028 | '-------------------------', PHP_EOL; 1029 | } 1030 | 1031 | $thread = new TestResultWaitTimeout( 1032 | null, null, $debug 1033 | ); 1034 | $thread->wait()->run()->wait(); 1035 | 1036 | $testCase->assertFalse($thread->getSuccess()); 1037 | $testCase->assertSame( 1038 | Thread::ERR_TIMEOUT_RESULT, 1039 | $thread->getLastErrorCode() 1040 | ); 1041 | $testCase->assertContains( 1042 | 'Exceeded timeout: thread work', 1043 | $thread->getLastErrorMsg() 1044 | ); 1045 | $testCase->assertSame( 1046 | Thread::STATE_WAIT, 1047 | $thread->getState() 1048 | ); 1049 | 1050 | $thread->cleanup(); 1051 | }, true); 1052 | } 1053 | 1054 | /** 1055 | * Job wait timeout test 1056 | * 1057 | * @author amal 1058 | * @group integrational 1059 | * @group timeout 1060 | * 1061 | * @requires extension posix 1062 | * @requires extension pcntl 1063 | */ 1064 | public function testJobWaitTimeout() 1065 | { 1066 | $debug = false; 1067 | $testCase = $this; 1068 | $this->processAsyncTest(function() use ($testCase, $debug) { 1069 | if ($debug) { 1070 | echo '-------------------------', PHP_EOL, 1071 | "Job wait (by worker) timeout test ", PHP_EOL, 1072 | '-------------------------', PHP_EOL; 1073 | } 1074 | 1075 | $thread = new TestJobWaitTimeout( 1076 | null, null, $debug 1077 | ); 1078 | $pid = $thread->getChildPid(); 1079 | $thread->wait(); 1080 | 1081 | // To certainly meet the timeout 1082 | usleep(20000); 1083 | $thread->getEventLoop()->loop(EVLOOP_NONBLOCK); 1084 | 1085 | if ($thread->getIsForked()) { 1086 | $thread->run()->wait(); 1087 | } 1088 | 1089 | $testCase->assertSame( 1090 | Thread::ERR_DEATH, 1091 | $thread->getLastErrorCode() 1092 | ); 1093 | $testCase->assertContains( 1094 | 'Worker is dead', 1095 | $thread->getLastErrorMsg() 1096 | ); 1097 | $testCase->assertSame( 1098 | Thread::STATE_WAIT, 1099 | $thread->getState() 1100 | ); 1101 | 1102 | $testCase->assertFalse($thread->getSuccess()); 1103 | $testCase->assertFalse($thread->getIsForked()); 1104 | 1105 | $testCase->assertNotSame($pid, $thread->getChildPid()); 1106 | $testCase->assertSame( 1107 | $thread->getParentPid(), 1108 | $thread->getChildPid() 1109 | ); 1110 | 1111 | $thread->cleanup(); 1112 | }, true); 1113 | } 1114 | 1115 | /** 1116 | * Parent check timeout test 1117 | * 1118 | * @author amal 1119 | * @group integrational 1120 | * @group timeout 1121 | * 1122 | * @requires extension posix 1123 | * @requires extension pcntl 1124 | */ 1125 | public function testParentCheckTimeout() 1126 | { 1127 | $debug = false; 1128 | $testCase = $this; 1129 | $this->processAsyncTest(function() use ($testCase, $debug) { 1130 | if ($debug) { 1131 | echo '-------------------------', PHP_EOL, 1132 | "Parent check timeout test ", PHP_EOL, 1133 | '-------------------------', PHP_EOL; 1134 | } 1135 | 1136 | $thread = new TestParentCheckTimeout( 1137 | null, null, $debug 1138 | ); 1139 | $childPid = $thread->wait()->getChildPid(); 1140 | $testCase->assertNotEmpty($childPid); 1141 | 1142 | $childChildPid = null; 1143 | $triggered = false; 1144 | $thread->bind( 1145 | TestParentCheckTimeout::EV_PID, 1146 | function($event_name, $e_data) 1147 | use (&$childChildPid, &$triggered, $testCase) 1148 | { 1149 | $triggered = true; 1150 | $testCase->assertSame( 1151 | TestParentCheckTimeout::EV_PID, 1152 | $event_name 1153 | ); 1154 | $childChildPid = $e_data; 1155 | } 1156 | )->run()->wait(); 1157 | 1158 | $testCase->assertTrue($triggered); 1159 | $testCase->assertNotEmpty($childChildPid); 1160 | 1161 | // Child is dead 1162 | $isAliveChild = Base::getProcessIsAlive($childPid); 1163 | $testCase->assertFalse($isAliveChild); 1164 | 1165 | // Child's child can be alive in that moment, so wait a litle 1166 | usleep(20000); 1167 | $i = 50; 1168 | do { 1169 | if (!$isAliveChildChild = Base::getProcessIsAlive($childChildPid)) { 1170 | break; 1171 | } 1172 | usleep(20000); 1173 | $i--; 1174 | } while ($i > 0); 1175 | $testCase->assertFalse($isAliveChildChild); 1176 | 1177 | $thread->cleanup(); 1178 | }, true); 1179 | } 1180 | 1181 | #endregion 1182 | 1183 | 1184 | 1185 | #region Auxiliary (helper) test methods 1186 | 1187 | /** 1188 | * Helper method for testing threads in asynchronous mode 1189 | * 1190 | * @param callable $callback 1191 | * @param bool $simple More simple test 1192 | * 1193 | * @throws \Exception 1194 | */ 1195 | function processAsyncTest($callback, $simple = false) 1196 | { 1197 | if (!Thread::$useForks) { 1198 | $this->markTestSkipped( 1199 | 'You need LibEvent, PCNTL and POSIX support' 1200 | .' with CLI sapi to fully test Threads' 1201 | ); 1202 | return; 1203 | } 1204 | 1205 | if ($simple) { 1206 | $ipcModes = array(Thread::$ipcDataMode => false); 1207 | $socketModes = array(Socket::$useSockets); 1208 | } else { 1209 | $ipcModes = array( 1210 | Thread::IPC_IGBINARY => 'igbinary_serialize', 1211 | Thread::IPC_SERIALIZE => false, 1212 | ); 1213 | $socketModes = array(true, false); 1214 | } 1215 | 1216 | $defSocketMode = Socket::$useSockets; 1217 | 1218 | foreach ($socketModes as $socketMode) { 1219 | Socket::$useSockets = $socketMode; 1220 | 1221 | foreach ($ipcModes as $mode => $check) { 1222 | if ($check && !function_exists($check)) { 1223 | continue; 1224 | } 1225 | 1226 | Thread::$ipcDataMode = $mode; 1227 | 1228 | try { 1229 | if (false === $callback()) { 1230 | break 2; 1231 | } 1232 | } catch (\Exception $e) { 1233 | if ($base = EventBase::getMainLoop(false)) { 1234 | $base->resource && $base->loopBreak(); 1235 | } 1236 | throw $e; 1237 | } 1238 | } 1239 | } 1240 | 1241 | Socket::$useSockets = $defSocketMode; 1242 | } 1243 | 1244 | 1245 | /** 1246 | * Thread 1247 | * 1248 | * @param bool $debug 1249 | * @param bool $bigResult 1250 | * @param bool $withChild 1251 | * @param bool $oneTask 1252 | */ 1253 | function processThread($debug = false, $bigResult = false, $withChild = false, $oneTask = false) 1254 | { 1255 | $num = 10; 1256 | 1257 | $async = Thread::$useForks; 1258 | if ($debug) { 1259 | echo '-----------------------', PHP_EOL, 1260 | "Thread test: ", ($async ? 'Async' : 'Sync'), PHP_EOL, 1261 | '-----------------------', PHP_EOL; 1262 | } 1263 | 1264 | if ($oneTask) { 1265 | $thread = new TestThreadOneTask(null, null, $debug); 1266 | } else { 1267 | $thread = $withChild 1268 | ? new TestThreadWithChilds(null, null, $debug) 1269 | : new TestThreadReturnFirstArgument(null, null, $debug); 1270 | } 1271 | 1272 | // You can override preforkWait property 1273 | // to TRUE to not wait thread at first time manually 1274 | $thread->wait(); 1275 | 1276 | if ($async) { 1277 | if ($oneTask) { 1278 | $this->assertFalse($thread->getIsForked()); 1279 | } else { 1280 | $this->assertTrue($thread->getIsForked()); 1281 | } 1282 | } 1283 | $this->assertFalse($thread->getIsChild()); 1284 | $this->assertSame(Thread::STATE_WAIT, $thread->getState()); 1285 | $this->assertEmpty( 1286 | $thread->getLastErrorCode(), 1287 | $thread->getLastErrorMsg() 1288 | ); 1289 | $this->assertEmpty($thread->getLastErrorMsg()); 1290 | $this->assertFalse($thread->getIsCleaning()); 1291 | 1292 | for ($i = 0; $i < $num; $i++) { 1293 | $value = $bigResult ? str_repeat($i, 100000) : $i; 1294 | $thread->run($value)->wait(); 1295 | $this->assertSame(Thread::STATE_WAIT, $thread->getState()); 1296 | $this->assertTrue( 1297 | $thread->getSuccess(), 1298 | 'Job failure - #' . $thread->getLastErrorCode() 1299 | . ' ' . $thread->getLastErrorMsg() 1300 | ); 1301 | $this->assertEquals($value, $thread->getResult()); 1302 | $this->assertEmpty( 1303 | $thread->getLastErrorCode(), 1304 | $thread->getLastErrorMsg() 1305 | ); 1306 | $this->assertEmpty($thread->getLastErrorMsg()); 1307 | } 1308 | 1309 | $this->assertSame($num, $thread->getStartedJobs()); 1310 | $this->assertSame($num, $thread->getSuccessfulJobs()); 1311 | $this->assertSame(0, $thread->getFailedJobs()); 1312 | 1313 | $thread->cleanup(); 1314 | $this->assertTrue($thread->getIsCleaning()); 1315 | $this->assertSame('TERM', $thread->getStateName()); 1316 | $this->assertSame('INIT', $thread->getStateName(Thread::STATE_INIT)); 1317 | } 1318 | 1319 | /** 1320 | * Thread 2 1321 | * 1322 | * @param bool $debug 1323 | */ 1324 | function processThread2($debug = false) 1325 | { 1326 | $async = Thread::$useForks; 1327 | if ($debug) { 1328 | echo '-------------------------', PHP_EOL, 1329 | "Additional thread test: ", ($async ? 'Async' : 'Sync'), PHP_EOL, 1330 | '-------------------------', PHP_EOL; 1331 | } 1332 | 1333 | 1334 | // Empty argument 1335 | $thread = new TestThreadReturnAllArguments( 1336 | null, null, $debug 1337 | ); 1338 | $thread->wait()->run(null)->wait(); 1339 | $this->assertTrue( 1340 | $thread->getSuccess(), 1341 | 'Job failure - #' . $thread->getLastErrorCode() 1342 | . ' ' . $thread->getLastErrorMsg() 1343 | ); 1344 | $this->assertSame(array(null), $thread->getResult()); 1345 | $this->assertEmpty( 1346 | $thread->getLastErrorCode(), 1347 | $thread->getLastErrorMsg() 1348 | ); 1349 | $this->assertEmpty($thread->getLastErrorMsg()); 1350 | $thread->cleanup(); 1351 | 1352 | 1353 | // Empty result 1354 | $thread = new TestThreadDoNothing( 1355 | null, null, $debug 1356 | ); 1357 | $thread->wait()->run()->wait(); 1358 | $this->assertTrue( 1359 | $thread->getSuccess(), 1360 | 'Job failure - #' . $thread->getLastErrorCode() 1361 | . ' ' . $thread->getLastErrorMsg() 1362 | ); 1363 | $this->assertSame(null, $thread->getResult()); 1364 | $this->assertEmpty( 1365 | $thread->getLastErrorCode(), 1366 | $thread->getLastErrorMsg() 1367 | ); 1368 | $this->assertEmpty($thread->getLastErrorMsg()); 1369 | $thread->cleanup(); 1370 | 1371 | 1372 | // Hooks, etc. 1373 | $thread = new TestThreadHooks( 1374 | null, null, $debug 1375 | ); 1376 | 1377 | $isAlive = new ReflectionMethod($thread, 'isAlive'); 1378 | $isAlive->setAccessible(true); 1379 | if ($async) { 1380 | $this->assertNotEmpty($thread->getEventLoop()); 1381 | $this->assertNotEmpty($thread->getPid()); 1382 | $this->assertNotEmpty($thread->getParentPid()); 1383 | $this->assertNotEmpty($thread->getChildPid()); 1384 | $this->assertTrue($thread->getIsForked()); 1385 | $this->assertTrue($isAlive->invoke($thread)); 1386 | } else { 1387 | $this->assertEmpty($thread->getEventLoop()); 1388 | $this->assertEmpty($thread->getPid()); 1389 | $this->assertEmpty($thread->getParentPid()); 1390 | $this->assertEmpty($thread->getChildPid()); 1391 | $this->assertFalse($thread->getIsForked()); 1392 | $this->assertFalse($isAlive->invoke($thread)); 1393 | } 1394 | 1395 | $this->assertSame(1, $thread->onLoadHookCalls); 1396 | $this->assertSame( 1397 | $async ? 0 : 1, 1398 | $thread->onForkHookCalls 1399 | ); 1400 | $this->assertSame(0, $thread->onShutdownHookCalls); 1401 | $this->assertSame(0, $thread->onCleanupHookCalls); 1402 | $thread->wait()->run()->wait(); 1403 | $this->assertTrue( 1404 | $thread->getSuccess(), 1405 | 'Job failure - #' . $thread->getLastErrorCode() 1406 | . ' ' . $thread->getLastErrorMsg() 1407 | ); 1408 | $this->assertSame(array(1, 1, 0, 0), $thread->getResult()); 1409 | $thread->cleanup(); 1410 | $this->assertSame(1, $thread->onLoadHookCalls); 1411 | $this->assertSame( 1412 | $async ? 0 : 1, 1413 | $thread->onForkHookCalls 1414 | ); 1415 | $this->assertSame(0, $thread->onShutdownHookCalls); 1416 | $this->assertSame(1, $thread->onCleanupHookCalls); 1417 | } 1418 | 1419 | /** 1420 | * Thread, random errors 1421 | * 1422 | * @param bool $debug 1423 | */ 1424 | function processThreadErrorable($debug = false) 1425 | { 1426 | $num = 10; 1427 | 1428 | $async = Thread::$useForks; 1429 | 1430 | if ($debug) { 1431 | echo '-----------------------', PHP_EOL, 1432 | "Thread errorable test: ", ($async ? 'Async' : 'Sync'), PHP_EOL, 1433 | '-----------------------', PHP_EOL; 1434 | } 1435 | 1436 | $thread = new TestThreadErrorable( 1437 | null, null, $debug 1438 | ); 1439 | 1440 | $value = $i = $j = 0; 1441 | $max = (int)($num*2); 1442 | 1443 | // You can override preforkWait property 1444 | // to TRUE to not wait thread at first time manually 1445 | $thread->wait(); 1446 | 1447 | while ($num > $i && $j <= $max) { 1448 | $j++; 1449 | $thread->run($value, $j)->wait(); 1450 | $state = $thread->getState(); 1451 | $this->assertSame(Thread::STATE_WAIT, $state); 1452 | if ($thread->getSuccess()) { 1453 | $result = $thread->getResult(); 1454 | $this->assertEquals($value, $result); 1455 | $value = ++$i; 1456 | } 1457 | } 1458 | 1459 | $this->assertSame($num, $i, 'All jobs must be done'); 1460 | if ($async) { 1461 | $this->assertTrue($j > $num); 1462 | } else { 1463 | $this->assertSame($num, $j); 1464 | } 1465 | 1466 | if ($async) { 1467 | $this->assertTrue($thread->getFailedJobs() > 0); 1468 | } 1469 | $this->assertSame( 1470 | $thread->getStartedJobs(), 1471 | $thread->getSuccessfulJobs() + $thread->getFailedJobs() 1472 | ); 1473 | 1474 | $thread->cleanup(); 1475 | } 1476 | 1477 | /** 1478 | * Thread, events 1479 | * 1480 | * @param bool $debug 1481 | * @param bool $bigData 1482 | * 1483 | * @throws \Exception 1484 | */ 1485 | function processThreadEvent($debug = false, $bigData = false) 1486 | { 1487 | $events = 11; 1488 | $num = 3; 1489 | 1490 | if ($debug) { 1491 | echo '-----------------------', PHP_EOL, 1492 | "Thread events test: ", (Thread::$useForks ? 'Async' : 'Sync'), PHP_EOL, 1493 | '-----------------------', PHP_EOL; 1494 | } 1495 | 1496 | $thread = new TestThreadEvents(null, null, $debug); 1497 | 1498 | $arg = mt_rand(19, 976); 1499 | if ($bigData) { 1500 | $arg = str_repeat($arg, 100000); 1501 | } 1502 | $last = 0; 1503 | $testCase = $this; 1504 | $cb = function($event, $e_data, $e_arg) use ($arg, $testCase, &$last) { 1505 | $testCase->assertSame($arg, $e_arg); 1506 | $testCase->assertSame(TestThreadEvents::EV_PROCESS, $event); 1507 | $testCase->assertEquals($last++, $e_data); 1508 | }; 1509 | $thread->bind(TestThreadEvents::EV_PROCESS, $cb, $arg); 1510 | 1511 | // You can override preforkWait property 1512 | // to TRUE to not wait thread at first time manually 1513 | $thread->wait(); 1514 | 1515 | $signalNotChecked = true; 1516 | for ($i = 0; $i < $num; $i++) { 1517 | $last = 0; 1518 | if ($signalNotChecked) { 1519 | $signalNotChecked = false; 1520 | $thread->run($events) 1521 | ->sendSignalToChild(SIGWINCH) 1522 | ->wait(); 1523 | } else { 1524 | $thread->run($events)->wait(); 1525 | } 1526 | $this->assertTrue( 1527 | $thread->getSuccess(), 1528 | 'Job failure - #' . $thread->getLastErrorCode() 1529 | . ' ' . $thread->getLastErrorMsg() 1530 | ); 1531 | } 1532 | 1533 | $this->assertSame($events, $last); 1534 | 1535 | $thread->cleanup(); 1536 | } 1537 | 1538 | /** 1539 | * Arguments mapping 1540 | * 1541 | * @param bool $debug 1542 | */ 1543 | function processThreadArgumentsMapping($debug = false) 1544 | { 1545 | $num = 3; 1546 | 1547 | $async = Thread::$useForks; 1548 | 1549 | if ($debug) { 1550 | echo '-----------------------', PHP_EOL, 1551 | "Thread arguments mapping test: ", ($async ? 'Async' : 'Sync'), PHP_EOL, 1552 | '-----------------------', PHP_EOL; 1553 | } 1554 | 1555 | $thread = new TestThreadArgumentsMapping( 1556 | null, null, $debug 1557 | ); 1558 | 1559 | // You can override preforkWait property 1560 | // to TRUE to not wait thread at first time manually 1561 | $thread->wait(); 1562 | 1563 | for ($i = 0; $i < $num; $i++) { 1564 | $a = mt_rand(99, 999); 1565 | $b = mt_rand(59, 999); 1566 | $c = mt_rand(13, 999); 1567 | $thread->run($a, $b, $c)->wait(); 1568 | $this->assertSame( 1569 | array($a, $b, $c), 1570 | $thread->getResult() 1571 | ); 1572 | } 1573 | 1574 | $thread->cleanup(); 1575 | } 1576 | 1577 | /** 1578 | * Closure thread 1579 | * 1580 | * @param bool $debug 1581 | */ 1582 | function processThreadClosure($debug = false) 1583 | { 1584 | $async = Thread::$useForks; 1585 | if ($debug) { 1586 | echo '-----------------------', PHP_EOL, 1587 | "Closure thread test: ", ($async ? 'Async' : 'Sync'), PHP_EOL, 1588 | '-----------------------', PHP_EOL; 1589 | } 1590 | 1591 | 1592 | // ---- 1593 | $expected = mt_rand(999, 99999); 1594 | $thread = SimpleThread::create(function() use ($expected) { 1595 | return $expected; 1596 | }, null, $debug); 1597 | $result = $thread->run()->wait()->getResult(); 1598 | $this->assertSame($expected, $result); 1599 | 1600 | 1601 | // ---- 1602 | $result = $thread->run()->wait()->getResult(); 1603 | $this->assertSame($expected, $result); 1604 | $thread->cleanup(); 1605 | 1606 | 1607 | // ---- 1608 | $expected = mt_rand(999, 99999); 1609 | $thread = SimpleThread::create(function($arg) { 1610 | return $arg; 1611 | }, null, $debug); 1612 | $result = $thread->run($expected)->wait()->getResult(); 1613 | $this->assertSame($expected, $result); 1614 | 1615 | 1616 | // ---- 1617 | $expected = mt_rand(999, 99999); 1618 | $result = $thread->run($expected)->wait()->getResult(); 1619 | $this->assertSame($expected, $result); 1620 | $thread->cleanup(); 1621 | } 1622 | 1623 | 1624 | /** 1625 | * Pool 1626 | * 1627 | * @param bool $debug 1628 | * @param bool $bigResult 1629 | * @param bool $withChild 1630 | * @param bool $oneTask 1631 | * 1632 | * @throws Exception 1633 | */ 1634 | function processPool($debug = false, $bigResult = false, $withChild = false, $oneTask = false) 1635 | { 1636 | $threads = 2; 1637 | $targetThreads = $threads+2; 1638 | $num = $targetThreads*5; 1639 | 1640 | if ($debug) { 1641 | echo '-----------------------', PHP_EOL, 1642 | "Thread pool test: ", (Thread::$useForks ? 'Async' : 'Sync'), PHP_EOL, 1643 | '-----------------------', PHP_EOL; 1644 | } 1645 | 1646 | 1647 | if ($oneTask) { 1648 | $thread = 'TestThreadOneTask'; 1649 | } else { 1650 | $thread = $withChild 1651 | ? 'TestThreadWithChilds' 1652 | : 'TestThreadReturnFirstArgument'; 1653 | } 1654 | $thread = __NAMESPACE__ . '\\' . $thread; 1655 | 1656 | $name = 'example'; 1657 | $pool = new ThreadPool($thread, $threads, null, $name, $debug); 1658 | 1659 | $this->assertNotEmpty($pool->getId()); 1660 | $this->assertSame($name, $pool->getPoolName()); 1661 | $this->assertEmpty($pool->getThreadProcessName()); 1662 | $this->assertSame($thread, $pool->getThreadClassName()); 1663 | 1664 | if (Thread::$useForks) { 1665 | $this->assertSame($threads, $pool->getThreadsCount()); 1666 | $this->assertSame($threads, $pool->getMaxThreads()); 1667 | 1668 | // We can not set number of threads lower than 1669 | // number of already created threads 1670 | $pool->setMaxThreads($threads-1); 1671 | $this->assertSame($threads, $pool->getThreadsCount()); 1672 | $this->assertSame($threads, $pool->getMaxThreads()); 1673 | 1674 | // But we can set more 1675 | $pool->setMaxThreads($targetThreads); 1676 | $this->assertSame($targetThreads, $pool->getThreadsCount()); 1677 | $this->assertSame($targetThreads, $pool->getMaxThreads()); 1678 | } else { 1679 | $this->assertSame(1, $pool->getThreadsCount()); 1680 | $this->assertSame(1, $pool->getMaxThreads()); 1681 | 1682 | $pool->setMaxThreads(8); 1683 | $this->assertSame(1, $pool->getThreadsCount()); 1684 | $this->assertSame(1, $pool->getMaxThreads()); 1685 | 1686 | $pool->setMaxThreads(0); 1687 | $this->assertSame(1, $pool->getThreadsCount()); 1688 | $this->assertSame(1, $pool->getMaxThreads()); 1689 | } 1690 | 1691 | $jobs = array(); 1692 | 1693 | $i = 0; 1694 | $left = $num; 1695 | $maxI = (int)ceil($num * 1.5); 1696 | $worked = array(); 1697 | do { 1698 | while ($left > 0 && $pool->hasWaiting()) { 1699 | $arg = mt_rand(1, 999); 1700 | if ($bigResult) { 1701 | $arg = str_repeat($arg, 100000); 1702 | } 1703 | $threadId = $pool->run($arg); 1704 | $this->assertNotEmpty($threadId); 1705 | $this->assertTrue( 1706 | !isset($jobs[$threadId]), 1707 | "Thread #$threadId is not failed correctly" 1708 | ); 1709 | $jobs[$threadId] = $arg; 1710 | $worked[$threadId] = true; 1711 | $left--; 1712 | } 1713 | $results = $pool->wait($failed); 1714 | $this->assertEmpty($failed, 'Failed results: ' . print_r($failed, true)); 1715 | if ($results) { 1716 | foreach ($results as $threadId => $res) { 1717 | $this->assertTrue(isset($jobs[$threadId]), "Thread #$threadId"); 1718 | $this->assertEquals($jobs[$threadId], $res, "Thread #$threadId"); 1719 | unset($jobs[$threadId]); 1720 | $num--; 1721 | } 1722 | } 1723 | $i++; 1724 | } while ($num > 0 && $i < $maxI); 1725 | 1726 | $this->assertSame(0, $num, 'All jobs must be done'); 1727 | 1728 | $this->assertSame( 1729 | $pool->getThreadsCount(), count($worked), 1730 | 'Worked threads count is not equals to real threads count' 1731 | ); 1732 | 1733 | $state = $pool->getThreadsState(); 1734 | $statistic = $pool->getThreadsStatistic(); 1735 | if (Thread::$useForks) { 1736 | $this->assertSame($targetThreads, $pool->getThreadsCount()); 1737 | $this->assertSame($targetThreads, count($state)); 1738 | $this->assertSame($targetThreads, count($statistic)); 1739 | } else { 1740 | $this->assertSame(1, $pool->getThreadsCount()); 1741 | $this->assertSame(1, count($state)); 1742 | $this->assertSame(1, count($statistic)); 1743 | } 1744 | foreach ($state as $s) { 1745 | $this->assertSame('WAIT', $s); 1746 | } 1747 | 1748 | $pool->cleanup(); 1749 | $this->assertSame(0, $pool->getThreadsCount()); 1750 | $this->assertEmpty($pool->getThreads()); 1751 | $this->assertFalse($pool->hasWaiting()); 1752 | $this->assertEmpty($pool->getThreadsState()); 1753 | } 1754 | 1755 | /** 1756 | * Pool 2 1757 | * 1758 | * @param bool $debug 1759 | * 1760 | * @throws Exception 1761 | */ 1762 | function processPool2($debug = false) 1763 | { 1764 | $async = Thread::$useForks; 1765 | 1766 | if ($debug) { 1767 | echo '-------------------------', PHP_EOL, 1768 | "Additional thread pool test: ", ($async ? 'Async' : 'Sync'), PHP_EOL, 1769 | '-------------------------', PHP_EOL; 1770 | } 1771 | 1772 | 1773 | // ReturnAllArguments 1774 | $threads = 2; 1775 | $thread = __NAMESPACE__ . '\TestThreadReturnAllArguments'; 1776 | $pName = 'ReturnAllArguments'; 1777 | $pool = new ThreadPool( 1778 | $thread, $threads, $pName, null, $debug 1779 | ); 1780 | 1781 | $this->assertSame($pName, $pool->getThreadProcessName()); 1782 | 1783 | $state = $pool->getThreadsState(); 1784 | $this->assertSame($async ? $threads : 1, count($state)); 1785 | foreach ($state as $s) { 1786 | $this->assertTrue('INIT' === $s || 'WAIT' === $s); 1787 | } 1788 | 1789 | if ($async) { 1790 | $catched = 0; 1791 | try { 1792 | $this->assertFalse($pool->run()); 1793 | } catch (Exception $e) { 1794 | $catched++; 1795 | } 1796 | try { 1797 | $this->assertFalse($pool->run('example')); 1798 | } catch (Exception $e) { 1799 | $catched++; 1800 | } 1801 | $this->assertSame(2, $catched); 1802 | } 1803 | 1804 | $data = array( 1805 | array(), 1806 | array(1), 1807 | array(1, 2), 1808 | array(1, 2, 3), 1809 | array(1, 2, 3, 4), 1810 | array(1, 2, 3, 4, 5), 1811 | array(null), 1812 | array(null, null), 1813 | ); 1814 | 1815 | $worked = $jobs = array(); 1816 | $i = 0; 1817 | $left = $num = count($data); 1818 | $maxI = ceil($num * 1.5); 1819 | do { 1820 | while ($left > 0 && $pool->hasWaiting()) { 1821 | $args = array_shift($data); 1822 | $threadId = call_user_func_array(array($pool, 'run'), $args); 1823 | 1824 | $this->assertNotEmpty($threadId); 1825 | $this->assertTrue( 1826 | !isset($jobs[$threadId]), 1827 | "Thread #$threadId is not failed correctly" 1828 | ); 1829 | 1830 | $jobs[$threadId] = $args; 1831 | $worked[$threadId] = true; 1832 | $left--; 1833 | } 1834 | $results = $pool->wait($failed); 1835 | $this->assertEmpty($failed, 'Failed results: ' . print_r($failed, true)); 1836 | if ($results) { 1837 | foreach ($results as $threadId => $res) { 1838 | $this->assertTrue(isset($jobs[$threadId]), "Thread #$threadId"); 1839 | $this->assertSame($jobs[$threadId], $res, "Thread #$threadId"); 1840 | unset($jobs[$threadId]); 1841 | $num--; 1842 | } 1843 | } 1844 | $i++; 1845 | } while ($num > 0 && $i < $maxI); 1846 | $this->assertSame(0, $num, 'All jobs must be done'); 1847 | $pool->cleanup(); 1848 | 1849 | 1850 | // TestThreadDoNothing 1851 | $threads = 2; 1852 | $thread = __NAMESPACE__ . '\TestThreadDoNothing'; 1853 | $pool = new ThreadPool( 1854 | $thread, $threads, null, null, $debug 1855 | ); 1856 | 1857 | $i = 0; 1858 | $left = $num = 1; 1859 | $maxI = 6; 1860 | do { 1861 | while ($left > 0 && $pool->hasWaiting()) { 1862 | $threadId = $pool->run(); 1863 | $jobs[$threadId] = null; 1864 | $worked[$threadId] = true; 1865 | $left--; 1866 | } 1867 | $results = $pool->wait($failed); 1868 | $this->assertEmpty($failed, 'Failed results: ' . print_r($failed, true)); 1869 | if ($results) { 1870 | foreach ($results as $threadId => $res) { 1871 | $this->assertTrue( 1872 | array_key_exists($threadId, $jobs), "Thread #$threadId" 1873 | ); 1874 | $this->assertSame($jobs[$threadId], $res, "Thread #$threadId"); 1875 | unset($jobs[$threadId]); 1876 | $num--; 1877 | break 2; 1878 | } 1879 | } 1880 | $i++; 1881 | } while ($i < $maxI); 1882 | $this->assertSame(0, $num, 'All jobs must be done'); 1883 | $pool->cleanup(); 1884 | } 1885 | 1886 | /** 1887 | * Pool, events 1888 | * 1889 | * @param bool $debug 1890 | * @param bool $bigData 1891 | * 1892 | * @throws Exception 1893 | */ 1894 | function processPoolEvent($debug = false, $bigData = false) 1895 | { 1896 | $events = 3; 1897 | $num = 12; 1898 | $threads = 3; 1899 | 1900 | $async = Thread::$useForks; 1901 | 1902 | if ($debug) { 1903 | echo '-----------------------', PHP_EOL, 1904 | "Thread pool events test: ", ($async ? 'Async' : 'Sync'), PHP_EOL, 1905 | '-----------------------', PHP_EOL; 1906 | } 1907 | 1908 | $pool = new ThreadPool( 1909 | __NAMESPACE__ . '\TestThreadEvents', 1910 | $threads, null, null, $debug 1911 | ); 1912 | 1913 | $arg = mt_rand(12, 987); 1914 | if ($bigData) { 1915 | $arg = str_repeat($arg, 100000); 1916 | } 1917 | $jobs = $worked = array(); 1918 | $test = $this; 1919 | $cb = function($event, $threadId, $e_data, $e_arg) use ($arg, $test, &$jobs) { 1920 | /** @var $test TestCase */ 1921 | $test->assertSame($arg, $e_arg); 1922 | $test->assertSame(TestThreadEvents::EV_PROCESS, $event); 1923 | if (!isset($jobs[$threadId])) { 1924 | $jobs[$threadId] = 0; 1925 | } 1926 | $test->assertEquals($jobs[$threadId]++, $e_data); 1927 | }; 1928 | $pool->bind(TestThreadEvents::EV_PROCESS, $cb, $arg); 1929 | 1930 | 1931 | $i = 0; 1932 | $left = $num; 1933 | $maxI = ceil($num * 1.5); 1934 | do { 1935 | while ($left > 0 && $pool->hasWaiting()) { 1936 | $threadId = $pool->run($events); 1937 | $this->assertNotEmpty($threadId); 1938 | $worked[$threadId] = true; 1939 | $left--; 1940 | } 1941 | $results = $pool->wait($failed); 1942 | $this->assertEmpty($failed, 'Failed results: ' . print_r($failed, true)); 1943 | if ($results) { 1944 | foreach ($results as $threadId => $res) { 1945 | $num--; 1946 | $this->assertTrue(isset($jobs[$threadId]), "Thread #$threadId"); 1947 | $this->assertSame($events, $jobs[$threadId]); 1948 | unset($jobs[$threadId]); 1949 | } 1950 | } 1951 | $i++; 1952 | } while ($num > 0 && $i < $maxI); 1953 | 1954 | $this->assertSame(0, $num, 'All jobs must be done'); 1955 | 1956 | $pool->cleanup(); 1957 | } 1958 | 1959 | /** 1960 | * Pool, errors 1961 | * 1962 | * @param bool $debug 1963 | * 1964 | * @throws Exception 1965 | */ 1966 | function processPoolErrorable($debug = false) 1967 | { 1968 | $num = 12; 1969 | $threads = 4; 1970 | 1971 | if ($debug) { 1972 | echo '-----------------------', PHP_EOL, 1973 | "Errorable thread pool test: ", (Thread::$useForks ? 'Async' : 'Sync'), PHP_EOL, 1974 | '-----------------------', PHP_EOL; 1975 | } 1976 | 1977 | $pool = new ThreadPool( 1978 | __NAMESPACE__ . '\TestThreadErrorable', 1979 | $threads, null, null, $debug 1980 | ); 1981 | 1982 | $jobs = array(); 1983 | 1984 | $i = $j = 0; 1985 | $left = $num; 1986 | $maxI = ceil($num * 2.5); 1987 | do { 1988 | while ($left > 0 && $pool->hasWaiting()) { 1989 | $arg = mt_rand(1000000, 200000000); 1990 | $threadId = $pool->run($arg, $j); 1991 | 1992 | $this->assertTrue( 1993 | !isset($jobs[$threadId]), 1994 | "Thread #$threadId is not failed correctly" 1995 | ); 1996 | 1997 | $jobs[$threadId] = $arg; 1998 | $left--; 1999 | $j++; 2000 | } 2001 | if ($results = $pool->wait($failed)) { 2002 | foreach ($results as $threadId => $res) { 2003 | $num--; 2004 | $this->assertTrue(isset($jobs[$threadId]), "Thread #$threadId"); 2005 | $this->assertEquals($jobs[$threadId], $res, "Thread #$threadId"); 2006 | unset($jobs[$threadId]); 2007 | } 2008 | } 2009 | if ($failed) { 2010 | foreach ($failed as $threadId => $errArray) { 2011 | list($errCode, $errMsg) = $errArray; 2012 | $this->assertTrue(isset($jobs[$threadId]), "Thread #$threadId"); 2013 | $this->assertNotEmpty($errCode, 'Error code needed'); 2014 | $this->assertTrue(is_int($errCode), 'Error code needed'); 2015 | $this->assertNotEmpty($errMsg, 'Error message needed'); 2016 | unset($jobs[$threadId]); 2017 | $left++; 2018 | } 2019 | } 2020 | $i++; 2021 | } while ($num > 0 && $i < $maxI); 2022 | 2023 | $this->assertSame(0, $num, 'All jobs must be done'); 2024 | 2025 | $pool->cleanup(); 2026 | $this->assertSame(0, $pool->getThreadsCount()); 2027 | $this->assertEmpty($pool->getThreads()); 2028 | $this->assertFalse($pool->hasWaiting()); 2029 | } 2030 | 2031 | /** 2032 | * Pool, external errors 2033 | * 2034 | * @param bool $debug 2035 | * 2036 | * @throws Exception 2037 | */ 2038 | function processPoolErrorableExternal($debug = false) 2039 | { 2040 | $num = 9; 2041 | $threads = 3; 2042 | 2043 | if ($debug) { 2044 | echo '------------------------------------------', PHP_EOL, 2045 | "Thread pool test with external stop: ", (Thread::$useForks ? 'Async' : 'Sync'), PHP_EOL, 2046 | '------------------------------------------', PHP_EOL; 2047 | } 2048 | 2049 | $pool = new ThreadPool( 2050 | __NAMESPACE__ . '\TestThreadErrorableExternal', 2051 | $threads, null, null, $debug 2052 | ); 2053 | 2054 | $argDebug = $debug && true; 2055 | $debugRef = new ReflectionMethod($pool, 'debug'); 2056 | $debugRef->setAccessible(true); 2057 | $debugCb = function ($msg) use ($argDebug, $debugRef, $pool) { 2058 | $argDebug && $debugRef->invoke($pool, $msg); 2059 | }; 2060 | 2061 | $jobs = array(); 2062 | 2063 | $i = $j = 0; 2064 | $left = $num; 2065 | $maxI = ceil($num * 2.5); 2066 | do { 2067 | while ($left > 0 && $pool->hasWaiting()) { 2068 | $arg = str_repeat( 2069 | mt_rand(100, 999), 2070 | $argDebug ? 3 : 10000 2071 | ); 2072 | 2073 | $threadId = $pool->run($arg, $j); 2074 | 2075 | $this->assertNotEmpty($threadId); 2076 | $debugCb( 2077 | "TEST: Thread #$threadId; Job argument on start [$arg]" 2078 | ); 2079 | 2080 | // Stop child 2081 | if (1 & $j) { 2082 | $threads = $pool->getThreads(); 2083 | $threads[$threadId]->sendSignalToChild( 2084 | ($j % 4) > 1 ? SIGTERM : SIGKILL 2085 | ); 2086 | $debugCb( 2087 | "TEST: Thread #$threadId stopped" 2088 | ); 2089 | unset($threads); 2090 | } 2091 | 2092 | $this->assertTrue( 2093 | !isset($jobs[$threadId]), 2094 | "Thread #$threadId is not failed correctly" 2095 | ); 2096 | 2097 | $jobs[$threadId] = $arg; 2098 | $left--; 2099 | $j++; 2100 | } 2101 | if ($results = $pool->wait($failed)) { 2102 | foreach ($results as $threadId => $res) { 2103 | $debugCb( 2104 | "TEST: Thread #$threadId; Job result [$res]" 2105 | ); 2106 | $this->assertTrue(isset($jobs[$threadId]), "Thread #$threadId"); 2107 | $this->assertEquals($jobs[$threadId], $res, "Thread #$threadId"); 2108 | unset($jobs[$threadId]); 2109 | $num--; 2110 | } 2111 | } 2112 | if ($failed) { 2113 | foreach ($failed as $threadId => $errArray) { 2114 | list($errCode, $errMsg) = $errArray; 2115 | $debugCb( 2116 | "TEST: Thread #$threadId; Job fail [#$errCode - $errMsg]" 2117 | ); 2118 | $this->assertTrue(isset($jobs[$threadId]), "Thread #$threadId"); 2119 | $this->assertNotEmpty($errCode, 'Error code needed'); 2120 | $this->assertTrue(is_int($errCode), 'Error code needed'); 2121 | $this->assertNotEmpty($errMsg, 'Error message needed'); 2122 | unset($jobs[$threadId]); 2123 | $left++; 2124 | } 2125 | } 2126 | $i++; 2127 | } while ($num > 0 && $i < $maxI); 2128 | 2129 | $this->assertSame(0, $num, 'All jobs must be done'); 2130 | 2131 | $pool->cleanup(); 2132 | } 2133 | 2134 | /** 2135 | * Exception in events triggering 2136 | * 2137 | * @param bool $debug 2138 | */ 2139 | function processExceptionInEventListener($debug = false) 2140 | { 2141 | if ($debug) { 2142 | echo '-----------------------', PHP_EOL, 2143 | "Exception in event listener test: ", (Thread::$useForks ? 'Async' : 'Sync'), PHP_EOL, 2144 | '-----------------------', PHP_EOL; 2145 | } 2146 | 2147 | $thread = new TestThreadEvents(null, null, $debug); 2148 | 2149 | $testCase = $this; 2150 | $thread->bind( 2151 | TestThreadEvents::EV_PROCESS, 2152 | function($event, $e_data, $e_arg) use ($testCase) { 2153 | $testCase->assertSame(null, $e_arg); 2154 | $testCase->assertSame( 2155 | TestThreadEvents::EV_PROCESS, 2156 | $event 2157 | ); 2158 | $testCase->assertEquals(0, $e_data); 2159 | 2160 | throw new InvalidArgumentException( 2161 | 'Example Message' 2162 | ); 2163 | } 2164 | ); 2165 | 2166 | try { 2167 | $thread->wait()->run(1)->wait(); 2168 | } catch (InvalidArgumentException $e) { 2169 | $catched = true; 2170 | $this->assertTrue($e instanceof InvalidArgumentException); 2171 | $this->assertContains( 2172 | 'Example Message', $e->getMessage() 2173 | ); 2174 | } 2175 | $this->assertFalse(empty($catched)); 2176 | 2177 | $thread->cleanup(); 2178 | } 2179 | 2180 | 2181 | /** 2182 | * Two different threads 2183 | */ 2184 | function processTwoDifferentThreads($debug = false) 2185 | { 2186 | $async = Thread::$useForks; 2187 | 2188 | if ($debug) { 2189 | echo '-----------------------', PHP_EOL, 2190 | "Different threads test ", ($async ? 'Async' : 'Sync'), PHP_EOL, 2191 | '-----------------------', PHP_EOL; 2192 | } 2193 | 2194 | 2195 | $arg1 = 123456789; 2196 | $arg2 = 987654321; 2197 | 2198 | $thread1 = new TestThreadReturnFirstArgument( 2199 | null, null, $debug 2200 | ); 2201 | $thread2 = new TestThreadReturnFirstArgument( 2202 | null, null, $debug 2203 | ); 2204 | 2205 | 2206 | $thread1->wait(); 2207 | $this->assertEmpty( 2208 | $thread1->getLastErrorCode(), 2209 | $thread1->getLastErrorMsg() 2210 | ); 2211 | $async && $this->assertTrue($thread1->getIsForked()); 2212 | $thread1->run($arg1); 2213 | 2214 | $thread2->wait(); 2215 | $this->assertEmpty( 2216 | $thread2->getLastErrorCode(), 2217 | $thread2->getLastErrorMsg() 2218 | ); 2219 | $async && $this->assertTrue($thread2->getIsForked()); 2220 | $thread2->run($arg2); 2221 | 2222 | $res2 = $thread2->wait()->getResult(); 2223 | $res1 = $thread1->wait()->getResult(); 2224 | $this->assertTrue($thread1->getSuccess()); 2225 | $this->assertSame($arg1, $res1); 2226 | $this->assertTrue($thread2->getSuccess()); 2227 | $this->assertSame($arg2, $res2); 2228 | 2229 | 2230 | $thread2->run($arg1); 2231 | $thread1->run($arg2); 2232 | 2233 | $res1 = $thread1->wait()->getResult(); 2234 | $res2 = $thread2->wait()->getResult(); 2235 | $this->assertSame($arg2, $res1); 2236 | $this->assertSame($arg1, $res2); 2237 | 2238 | 2239 | $thread1->run($arg1); 2240 | $thread2->run($arg2); 2241 | 2242 | $res1 = $thread1->wait()->getResult(); 2243 | $res2 = $thread2->wait()->getResult(); 2244 | $this->assertSame($arg1, $res1); 2245 | $this->assertSame($arg2, $res2); 2246 | 2247 | 2248 | $thread2->cleanup(); 2249 | 2250 | $res1 = $thread1->run($arg2)->wait()->getResult(); 2251 | $this->assertSame($arg2, $res1); 2252 | 2253 | 2254 | $thread1->cleanup(); 2255 | } 2256 | 2257 | 2258 | /** 2259 | * Event callback for tests 2260 | */ 2261 | function processEventCallback($event, $e_data, $e_arg) 2262 | { 2263 | $this->assertSame(TestThreadEvents::EV_PROCESS, $event); 2264 | $this->assertEquals(0, $e_arg); 2265 | $this->assertNotSame(null, $e_data); 2266 | } 2267 | 2268 | #endregion 2269 | } 2270 | 2271 | 2272 | 2273 | #region Test mocks 2274 | 2275 | /** 2276 | * Test thread 2277 | */ 2278 | class TestThreadArgumentsMapping extends Thread 2279 | { 2280 | /** 2281 | * {@inheritdoc} 2282 | */ 2283 | protected $timeoutMasterResultWait = 2; 2284 | 2285 | /** 2286 | * {@inheritdoc} 2287 | */ 2288 | protected $argumentsMapping = true; 2289 | 2290 | /** 2291 | * {@inheritdoc} 2292 | */ 2293 | function process($a, $b, $c) 2294 | { 2295 | return array($a, $b, $c); 2296 | } 2297 | } 2298 | 2299 | /** 2300 | * Test thread 2301 | */ 2302 | class TestThreadReturnFirstArgument extends Thread 2303 | { 2304 | /** 2305 | * {@inheritdoc} 2306 | */ 2307 | protected $timeoutMasterInitWait = 1; 2308 | 2309 | /** 2310 | * {@inheritdoc} 2311 | */ 2312 | protected $timeoutMasterResultWait = 10; 2313 | 2314 | /** 2315 | * {@inheritdoc} 2316 | */ 2317 | protected $timeoutWorkerJobWait = 15; 2318 | 2319 | 2320 | /** 2321 | * {@inheritdoc} 2322 | */ 2323 | function process() 2324 | { 2325 | $param = $this->getParam(0); 2326 | if ($this->debug && is_scalar($param) && strlen($param) < 50) { 2327 | $this->debug("TEST: Job argument in worker [$param]"); 2328 | } 2329 | return $param; 2330 | } 2331 | } 2332 | 2333 | /** 2334 | * Test thread 2335 | */ 2336 | class TestThreadOneTask extends TestThreadReturnFirstArgument 2337 | { 2338 | /** 2339 | * {@inheritdoc} 2340 | */ 2341 | protected $multitask = false; 2342 | 2343 | /** 2344 | * {@inheritdoc} 2345 | */ 2346 | protected $prefork = false; 2347 | } 2348 | 2349 | /** 2350 | * Test thread 2351 | */ 2352 | class TestThreadDoNothing extends TestThreadReturnFirstArgument 2353 | { 2354 | /** 2355 | * {@inheritdoc} 2356 | */ 2357 | function process() {} 2358 | } 2359 | 2360 | /** 2361 | * Test thread 2362 | */ 2363 | class TestThreadHooks extends TestThreadReturnFirstArgument 2364 | { 2365 | /** @var int */ 2366 | public $onLoadHookCalls = 0; 2367 | 2368 | /** @var int */ 2369 | public $onForkHookCalls = 0; 2370 | 2371 | /** @var int */ 2372 | public $onShutdownHookCalls = 0; 2373 | 2374 | /** @var int */ 2375 | public $onCleanupHookCalls = 0; 2376 | 2377 | 2378 | /** 2379 | * {@inheritdoc} 2380 | */ 2381 | protected function onLoad() 2382 | { 2383 | $this->onLoadHookCalls++; 2384 | } 2385 | 2386 | /** 2387 | * {@inheritdoc} 2388 | */ 2389 | protected function onFork() 2390 | { 2391 | $this->onForkHookCalls++; 2392 | } 2393 | 2394 | /** 2395 | * {@inheritdoc} 2396 | */ 2397 | protected function onShutdown() 2398 | { 2399 | $this->onShutdownHookCalls++; 2400 | } 2401 | 2402 | /** 2403 | * {@inheritdoc} 2404 | */ 2405 | protected function onCleanup() 2406 | { 2407 | $this->onCleanupHookCalls++; 2408 | } 2409 | 2410 | 2411 | /** 2412 | * {@inheritdoc} 2413 | */ 2414 | function process() 2415 | { 2416 | return array( 2417 | $this->onLoadHookCalls, 2418 | $this->onForkHookCalls, 2419 | $this->onShutdownHookCalls, 2420 | $this->onCleanupHookCalls 2421 | ); 2422 | } 2423 | } 2424 | 2425 | /** 2426 | * Test thread 2427 | */ 2428 | class TestThreadReturnAllArguments extends TestThreadReturnFirstArgument 2429 | { 2430 | /** 2431 | * {@inheritdoc} 2432 | */ 2433 | function process() 2434 | { 2435 | return $this->getParams(); 2436 | } 2437 | } 2438 | 2439 | /** 2440 | * Test thread 2441 | */ 2442 | class TestThreadErrorable extends TestThreadReturnFirstArgument 2443 | { 2444 | /** 2445 | * {@inheritdoc} 2446 | */ 2447 | protected $timeoutMasterResultWait = 0.1; 2448 | 2449 | /** 2450 | * {@inheritdoc} 2451 | */ 2452 | function process() 2453 | { 2454 | if (1 & ($arg = (int)$this->getParam(1))) { 2455 | // Emulate terminating 2456 | $signo = ($arg % 4) > 1 ? SIGTERM : SIGKILL; 2457 | if ($this->debug) { 2458 | $signame = Base::getSignalName($signo); 2459 | $this->debug("TEST: Emulate terminating with $signame ($signo)"); 2460 | } 2461 | $this->sendSignalToChild($signo); 2462 | } 2463 | return parent::process(); 2464 | } 2465 | } 2466 | 2467 | /** 2468 | * Test thread 2469 | */ 2470 | class TestThreadErrorableExternal extends TestThreadReturnFirstArgument 2471 | { 2472 | /** 2473 | * {@inheritdoc} 2474 | */ 2475 | protected $timeoutMasterResultWait = 0.1; 2476 | } 2477 | 2478 | /** 2479 | * Test thread 2480 | */ 2481 | class TestThreadWithChilds extends TestThreadReturnFirstArgument 2482 | { 2483 | /** 2484 | * Enable prefork waiting to test it 2485 | */ 2486 | protected $preforkWait = true; 2487 | 2488 | /** 2489 | * {@inheritdoc} 2490 | */ 2491 | function process() 2492 | { 2493 | /** @noinspection PhpUnusedLocalVariableInspection */ 2494 | $res = `echo 1`; 2495 | return $this->getParam(0); 2496 | } 2497 | } 2498 | 2499 | /** 2500 | * Test thread 2501 | */ 2502 | class TestThreadEvents extends TestThreadReturnFirstArgument 2503 | { 2504 | const EV_PROCESS = 'process'; 2505 | 2506 | /** 2507 | * {@inheritdoc} 2508 | */ 2509 | protected $timeoutMasterResultWait = 2; 2510 | 2511 | /** 2512 | * {@inheritdoc} 2513 | */ 2514 | protected $prefork = false; 2515 | 2516 | /** 2517 | * {@inheritdoc} 2518 | */ 2519 | function process() 2520 | { 2521 | $events = $this->getParam(0); 2522 | for ($i = 0; $i < $events; $i++) { 2523 | $this->trigger(self::EV_PROCESS, $i); 2524 | } 2525 | } 2526 | } 2527 | 2528 | /** 2529 | * Test thread 2530 | */ 2531 | class TestPreforkWaitTimeout extends Thread 2532 | { 2533 | /** 2534 | * {@inheritdoc} 2535 | */ 2536 | protected $preforkWait = true; 2537 | 2538 | /** 2539 | * {@inheritdoc} 2540 | */ 2541 | protected $timeoutMasterInitWait = 0.000001; 2542 | 2543 | /** 2544 | * {@inheritdoc} 2545 | */ 2546 | protected function onFork() 2547 | { 2548 | // To certainly meet the timeout 2549 | usleep(20000); 2550 | } 2551 | 2552 | /** 2553 | * {@inheritdoc} 2554 | */ 2555 | function process() {} 2556 | } 2557 | 2558 | /** 2559 | * Test thread 2560 | */ 2561 | class TestResultWaitTimeout extends Thread 2562 | { 2563 | /** 2564 | * {@inheritdoc} 2565 | */ 2566 | protected $timeoutMasterResultWait = 0.000001; 2567 | 2568 | /** 2569 | * {@inheritdoc} 2570 | */ 2571 | function process() 2572 | { 2573 | // To certainly meet the timeout 2574 | usleep(20000); 2575 | } 2576 | } 2577 | 2578 | /** 2579 | * Test thread 2580 | */ 2581 | class TestJobWaitTimeout extends Thread 2582 | { 2583 | /** 2584 | * {@inheritdoc} 2585 | */ 2586 | protected $timeoutWorkerJobWait = 0.000001; 2587 | 2588 | /** 2589 | * {@inheritdoc} 2590 | */ 2591 | protected $listenMasterSignals = false; 2592 | 2593 | /** 2594 | * {@inheritdoc} 2595 | */ 2596 | function process() {} 2597 | } 2598 | 2599 | /** 2600 | * Test thread 2601 | */ 2602 | class TestParentCheckTimeout extends Thread 2603 | { 2604 | const EV_PID = 'pid'; 2605 | 2606 | /** 2607 | * {@inheritdoc} 2608 | */ 2609 | protected $timeoutMasterResultWait = 0.1; 2610 | 2611 | /** 2612 | * {@inheritdoc} 2613 | */ 2614 | function process() 2615 | { 2616 | // Create new thread. Set debug flag from this thread 2617 | $child = new TestParentCheckTimeoutChild( 2618 | null, null, $this->debug, array( 2619 | 'eventLoop' => new EventBase() 2620 | ) 2621 | ); 2622 | 2623 | // Send child PID to parent via event 2624 | $childPid = $child->wait()->getChildPid(); 2625 | $this->trigger(self::EV_PID, $childPid); 2626 | 2627 | // Emulate terminating with small timeout 2628 | $this->sendSignalToChild(SIGKILL); 2629 | } 2630 | } 2631 | 2632 | /** 2633 | * Test thread 2634 | */ 2635 | class TestParentCheckTimeoutChild extends Thread 2636 | { 2637 | /** 2638 | * {@inheritdoc} 2639 | */ 2640 | protected $intervalWorkerMasterChecks = 0.001; 2641 | 2642 | /** 2643 | * {@inheritdoc} 2644 | */ 2645 | function process() {} 2646 | 2647 | /** 2648 | * {@inheritdoc} 2649 | */ 2650 | protected function registerEventSignals($allSignals = true) {} 2651 | } 2652 | 2653 | /** 2654 | * Test thread 2655 | */ 2656 | class TestSignalsHandling extends TestThreadReturnFirstArgument 2657 | { 2658 | /** 2659 | * @var int 2660 | */ 2661 | protected $catchedSignals = 0; 2662 | 2663 | /** 2664 | * @var int 2665 | */ 2666 | public static $catchedSignalsInParent = 0; 2667 | 2668 | 2669 | /** 2670 | * {@inheritdoc} 2671 | */ 2672 | function process() 2673 | { 2674 | return $this->sendSignalToParent(SIGUSR2)->catchedSignals; 2675 | } 2676 | 2677 | 2678 | /** 2679 | * Child SIGUSR1 handler 2680 | */ 2681 | protected function sigUsr1() 2682 | { 2683 | $this->catchedSignals++; 2684 | } 2685 | 2686 | /** 2687 | * Parent SIGUSR2 handler 2688 | */ 2689 | protected static function mSigUsr2() 2690 | { 2691 | self::$catchedSignalsInParent++; 2692 | } 2693 | } 2694 | 2695 | #endregion 2696 | -------------------------------------------------------------------------------- /ThreadPool.php: -------------------------------------------------------------------------------- 1 | 18 | * @license MIT 19 | */ 20 | class ThreadPool 21 | { 22 | /** 23 | * Default pool name 24 | */ 25 | const DEFAULT_NAME = 'base'; 26 | 27 | /** 28 | * All created pools count 29 | * 30 | * @var int 31 | */ 32 | protected static $allPoolsCount = 0; 33 | 34 | 35 | #region Internal properties 36 | 37 | /** 38 | * Maximum threads number in pool 39 | */ 40 | protected $maxThreads = 4; 41 | 42 | /** 43 | * Internal unique pool id 44 | * 45 | * @var int 46 | */ 47 | protected $id; 48 | 49 | /** 50 | * Internal pool name 51 | * 52 | * @var string 53 | */ 54 | protected $poolName; 55 | 56 | /** 57 | * Thread class name 58 | * 59 | * @var string 60 | */ 61 | protected $threadClassName; 62 | 63 | /** 64 | * Thread process name 65 | * 66 | * @var null|string 67 | */ 68 | protected $threadProcessName; 69 | 70 | /** 71 | * Event listeners 72 | */ 73 | protected $listeners = array(); 74 | 75 | /** 76 | * Threads in pool (threadId => thread) 77 | * 78 | * @var Thread[] 79 | */ 80 | protected $threads = array(); 81 | 82 | /** 83 | * Waiting for job threads IDs (threadId => threadId) 84 | * 85 | * @var int[] 86 | */ 87 | protected $waitingForJob = array(); 88 | 89 | /** 90 | * Waiting for result fetch threads IDs (threadId => threadId) 91 | * 92 | * @var int[] 93 | */ 94 | protected $waitingForFetch = array(); 95 | 96 | /** 97 | * Working threads IDs (threadId => threadId) 98 | * 99 | * @var int[] 100 | */ 101 | protected $working = array(); 102 | 103 | /** 104 | * Initializing threads IDs (threadId => threadId) 105 | * 106 | * @var int[] 107 | */ 108 | protected $initializing = array(); 109 | 110 | /** 111 | * Failed threads IDs (threadId => [errorCode, errorMsg]) 112 | * 113 | * @var array[] 114 | */ 115 | protected $failures = array(); 116 | 117 | /** 118 | * Results flags (threadId => threadId) 119 | */ 120 | protected $resultFlags = array(); 121 | 122 | /** 123 | * Received results (threadId => result) 124 | */ 125 | protected $results = array(); 126 | 127 | /** 128 | * Current threads count 129 | */ 130 | protected $threadsCount = 0; 131 | 132 | /** 133 | * Flag for detached state. 134 | * Enabled in child process after forking. 135 | */ 136 | protected $detached = false; 137 | 138 | /** 139 | * Whether to show debugging information. 140 | * DO NOT USE IN PRODUCTION! 141 | * 142 | * @internal 143 | */ 144 | public $debug = false; 145 | 146 | #endregion 147 | 148 | 149 | 150 | #region Initialization 151 | 152 | /** 153 | * Thread pool initialization 154 | * 155 | * @param string $threadName Thread class name 156 | * @param int $maxThreads Maximum threads number in pool 157 | * @param string $pName Thread process name 158 | * @param string $name Pool name 159 | * @param bool $debug Whether to enable debug mode 160 | * 161 | * @internal 162 | */ 163 | public function __construct($threadName, $maxThreads = null, 164 | $pName = null, $name = null, $debug = false) 165 | { 166 | $debug && $this->debug = true; 167 | 168 | $this->id = ++self::$allPoolsCount; 169 | $this->poolName = $name ?: self::DEFAULT_NAME; 170 | $this->threadClassName = $threadName; 171 | 172 | // @codeCoverageIgnoreStart 173 | $this->debug( 174 | "Pool of '$threadName' threads created (" 175 | . ltrim(spl_object_hash($this), '0') . ')' 176 | ); 177 | // @codeCoverageIgnoreEnd 178 | 179 | if (Thread::$useForks) { 180 | if ($maxThreads > 0) { 181 | $this->maxThreads = (int)$maxThreads; 182 | } 183 | } else { 184 | $this->maxThreads = 1; 185 | } 186 | 187 | if ($pName) { 188 | $this->threadProcessName = $pName; 189 | } 190 | 191 | $this->createAllThreads(); 192 | } 193 | 194 | /** 195 | * Creates threads while pool has free slots 196 | */ 197 | protected function createAllThreads() 198 | { 199 | $count = &$this->threadsCount; 200 | $tMax = $this->maxThreads; 201 | if ($count < $tMax) { 202 | $tName = $this->threadClassName; 203 | $pName = $this->threadProcessName; 204 | $debug = $this->debug; 205 | do { 206 | /** @var $thread Thread */ 207 | $thread = new $tName($pName, $this, $debug); 208 | $count++; 209 | 210 | // @codeCoverageIgnoreStart 211 | $debug && $this->debug( 212 | "Thread #{$thread->getId()} created" 213 | ); 214 | // @codeCoverageIgnoreEnd 215 | } while ($count < $tMax); 216 | } 217 | } 218 | 219 | #endregion 220 | 221 | 222 | 223 | #region Cleanup 224 | 225 | /** 226 | * Destruction 227 | * 228 | * @internal 229 | */ 230 | public function __destruct() 231 | { 232 | // @codeCoverageIgnoreStart 233 | $this->debug && $this->debug( 234 | 'Destructor (' . ltrim(spl_object_hash($this), '0') . ')' 235 | ); 236 | // @codeCoverageIgnoreEnd 237 | $this->cleanup(); 238 | } 239 | 240 | /** 241 | * Pool cleanup 242 | */ 243 | public function cleanup() 244 | { 245 | // @codeCoverageIgnoreStart 246 | ($debug = $this->debug) && $this->debug( 247 | 'Cleanup (' . ltrim(spl_object_hash($this), '0') . ')' 248 | . ($this->detached ? ' (FORCED - redundant instance)' : '') 249 | ); 250 | // @codeCoverageIgnoreEnd 251 | 252 | // Destroy all threads 253 | if (!$this->detached) { 254 | foreach ($this->threads as $thread) { 255 | $thread->cleanup(); 256 | } 257 | } 258 | 259 | // Clean all array fields in pool 260 | $this->listeners = 261 | $this->threads = 262 | $this->waitingForJob = 263 | $this->waitingForFetch = 264 | $this->working = 265 | $this->initializing = 266 | $this->failures = 267 | $this->results = array(); 268 | } 269 | 270 | /** 271 | * Pool detaching (special cleanup 272 | * for pool instance in child process) 273 | * 274 | * @internal 275 | * 276 | * @codeCoverageIgnore Called only in child (can't get coverage from another process) 277 | */ 278 | public function detach() 279 | { 280 | if (!$this->detached) { 281 | $this->detached = true; 282 | $this->cleanup(); 283 | 284 | $this->debug && $this->debug( 285 | "Thread pool is detached" 286 | ); 287 | } else { 288 | $this->debug && $this->debug( 289 | "Thread pool is already detached" 290 | ); 291 | } 292 | } 293 | 294 | #endregion 295 | 296 | 297 | 298 | /** 299 | * Starts job in one of the idle threads 300 | * 301 | * @see hasWaiting 302 | * @see wait 303 | * 304 | * @return int ID of thread that started the job 305 | * 306 | * @throws Exception if no free threads in pool 307 | */ 308 | public function run() 309 | { 310 | if ($waiting = $this->waitingForJob) { 311 | $threadId = reset($waiting); 312 | $thread = $this->threads[$threadId]; 313 | 314 | // @codeCoverageIgnoreStart 315 | ($debug = $this->debug) && $this->debug( 316 | "Starting job in thread #{$threadId}..." 317 | ); 318 | // @codeCoverageIgnoreEnd 319 | 320 | // Use strict call for speedup 321 | // if number of arguments is not too big 322 | $args = func_get_args(); 323 | $count = count($args); 324 | if (0 === $count) { 325 | $thread->run(); 326 | } else if (1 === $count) { 327 | $thread->run($args[0]); 328 | } else if (2 === $count) { 329 | $thread->run($args[0], $args[1]); 330 | } else if (3 === $count) { 331 | $thread->run($args[0], $args[1], $args[2]); 332 | } else { 333 | call_user_func_array(array($thread, 'run'), $args); 334 | } 335 | 336 | // @codeCoverageIgnoreStart 337 | $debug && $this->debug( 338 | "Thread #$threadId started" 339 | ); 340 | // @codeCoverageIgnoreEnd 341 | 342 | return $threadId; 343 | } 344 | 345 | // Strict approach 346 | throw new Exception('No threads waiting for the job'); 347 | } 348 | 349 | 350 | 351 | #region Master waiting 352 | 353 | /** 354 | * Waits for waiting threads in pool 355 | * 356 | * @param array $failed Array of failed threads 357 | * 358 | * @return array Returns array of results (can be empty) 359 | * 360 | * @throws Exception 361 | */ 362 | public function wait(&$failed = null) 363 | { 364 | if ($this->waitingForFetch) { 365 | return $this->getResults($failed); 366 | } 367 | 368 | $threadIds = $this->working + $this->initializing; 369 | if ($threadIds) { 370 | // @codeCoverageIgnoreStart 371 | $this->debug && $this->debug( 372 | 'Waiting for threads: #' . join(', #', $threadIds) 373 | ); 374 | // @codeCoverageIgnoreEnd 375 | 376 | Thread::waitThreads($threadIds); 377 | } else { 378 | // Should not be called 379 | // @codeCoverageIgnoreStart 380 | throw new Exception( 381 | 'Nothing to wait in pool' 382 | ); 383 | // @codeCoverageIgnoreEnd 384 | } 385 | 386 | return $this->getResults($failed); 387 | } 388 | 389 | /** 390 | * Returns array of results by threads 391 | * 392 | * @param array &$failed Array of failed threads 393 | * 394 | * @return array 395 | */ 396 | protected function getResults(&$failed = null) 397 | { 398 | $results = $this->results; 399 | $failed = $this->failures; 400 | 401 | $this->waitingForJob += $this->waitingForFetch; 402 | 403 | // @codeCoverageIgnoreStart 404 | if ($this->debug) { 405 | $this->debug( 406 | $results 407 | ? 'Fetching results for threads: #' 408 | . join(', #', array_keys($results)) 409 | : "No results to return" 410 | ); 411 | $failed && $this->debug( 412 | 'Fetching FAILED results for threads: #' 413 | . join(', #', array_keys($failed)) 414 | ); 415 | foreach ($this->waitingForFetch as $threadId) { 416 | $this->debug( 417 | "Thread #{$threadId} is marked as waiting for job" 418 | ); 419 | } 420 | } 421 | // @codeCoverageIgnoreEnd 422 | 423 | $this->results = 424 | $this->resultFlags = 425 | $this->failures = 426 | $this->waitingForFetch = array(); 427 | 428 | return $results; 429 | } 430 | 431 | #endregion 432 | 433 | 434 | 435 | #region Thread events dispatching 436 | 437 | /** 438 | * Connects a listener to a given event. 439 | * 440 | * @see trigger 441 | * 442 | * @param string $event

443 | * An event name. 444 | *

445 | * @param callback $listener

446 | * Callback to be called when the matching event occurs. 447 | *
function(string $event_name, int $thread_id, mixed $event_data, mixed $event_arg){} 448 | *

449 | * @param mixed $arg

450 | * Additional argument for callback. 451 | *

452 | */ 453 | public function bind($event, $listener, $arg = null) 454 | { 455 | 456 | if (!isset($this->listeners[$event])) { 457 | $this->listeners[$event] = array(); 458 | } 459 | $this->listeners[$event][] = array($listener, $arg); 460 | 461 | // @codeCoverageIgnoreStart 462 | $this->debug && $this->debug( 463 | "New listener binded on event [$event]" 464 | ); 465 | // @codeCoverageIgnoreEnd 466 | } 467 | 468 | /** 469 | * Notifies all listeners of a given event. 470 | * 471 | * @see bind 472 | * 473 | * @param string $event An event name 474 | * @param int $threadId Id of thread that caused the event 475 | * @param mixed $data Event data for callback 476 | */ 477 | public function trigger($event, $threadId, $data = null) 478 | { 479 | // @codeCoverageIgnoreStart 480 | ($debug = $this->debug) && $this->debug( 481 | "Triggering event \"$event\" on pool" 482 | ); 483 | // @codeCoverageIgnoreEnd 484 | 485 | if (!empty($this->listeners[$event])) { 486 | // @codeCoverageIgnoreStart 487 | $debug && $this->debug( 488 | "Pool has event listeners. Notify them..." 489 | ); 490 | // @codeCoverageIgnoreEnd 491 | 492 | /** @var $cb callback */ 493 | foreach ($this->listeners[$event] as $l) { 494 | list($cb, $arg) = $l; 495 | if ($cb instanceof \Closure) { 496 | $cb($event, $threadId, $data, $arg); 497 | } else { 498 | call_user_func( 499 | $cb, $event, $threadId, $data, $arg 500 | ); 501 | } 502 | } 503 | } 504 | } 505 | 506 | #endregion 507 | 508 | 509 | 510 | #region Getters/Setters 511 | 512 | /** 513 | * Returns if pool has waiting threads 514 | * 515 | * @return bool 516 | */ 517 | public function hasWaiting() 518 | { 519 | return (bool)$this->waitingForJob; 520 | } 521 | 522 | 523 | /** 524 | * Returns internal unique pool id 525 | * 526 | * @return int 527 | */ 528 | public function getId() 529 | { 530 | return $this->id; 531 | } 532 | 533 | /** 534 | * Returns internal pool name 535 | * 536 | * @return string 537 | */ 538 | public function getPoolName() 539 | { 540 | return $this->poolName; 541 | } 542 | 543 | 544 | /** 545 | * Returns thread process name 546 | * 547 | * @return null|string 548 | */ 549 | public function getThreadProcessName() 550 | { 551 | return $this->threadProcessName; 552 | } 553 | 554 | /** 555 | * Returns thread class name 556 | * 557 | * @return string 558 | */ 559 | public function getThreadClassName() 560 | { 561 | return $this->threadClassName; 562 | } 563 | 564 | 565 | /** 566 | * Returns pool threads 567 | * 568 | * @return Thread[] array of threads (threadId => thread) 569 | */ 570 | public function getThreads() 571 | { 572 | return $this->threads; 573 | } 574 | 575 | /** 576 | * Returns status of all threads in pool 577 | * 578 | * @return string[] Array of statuses (threadId => stateName) 579 | */ 580 | public function getThreadsState() 581 | { 582 | $state = array(); 583 | foreach ($this->threads as $threadId => $thread) { 584 | $state[$threadId] = $thread->getStateName(); 585 | } 586 | return $state; 587 | } 588 | 589 | /** 590 | * Returns statistic for all threads in pool 591 | * 592 | * @return array[] Array of data (threadId => data array) 593 | * with fields: state, started_jobs, successful_jobs, failed_jobs 594 | */ 595 | public function getThreadsStatistic() 596 | { 597 | $state = array(); 598 | foreach ($this->threads as $threadId => $thread) { 599 | $state[$threadId] = array( 600 | 'state' => $thread->getStateName(), 601 | 'started_jobs' => $thread->getStartedJobs(), 602 | 'successful_jobs' => $thread->getSuccessfulJobs(), 603 | 'failed_jobs' => $thread->getFailedJobs(), 604 | ); 605 | } 606 | return $state; 607 | } 608 | 609 | /** 610 | * Returns current threads count 611 | */ 612 | public function getThreadsCount() 613 | { 614 | return $this->threadsCount; 615 | } 616 | 617 | 618 | /** 619 | * Returns maximum threads number in pool. 620 | * 621 | * You can increase this number with 622 | * {@link setMaxThreads}, but not decrease 623 | * 624 | * @see setMaxThreads 625 | * 626 | * @return int 627 | */ 628 | public function getMaxThreads() 629 | { 630 | return $this->maxThreads; 631 | } 632 | 633 | /** 634 | * Sets maximum threads number 635 | * 636 | * @param int $value 637 | */ 638 | public function setMaxThreads($value) 639 | { 640 | // Filter value 641 | if ($value < $this->threadsCount) { 642 | $value = $this->threadsCount; 643 | } else if (!Thread::$useForks || $value < 1) { 644 | $value = 1; 645 | } else { 646 | $value = (int)$value; 647 | } 648 | 649 | // Apply new value 650 | if ($value !== $this->maxThreads) { 651 | // @codeCoverageIgnoreStart 652 | $this->debug && $this->debug( 653 | "The number of threads changed: " 654 | . "{$this->maxThreads} => $value" 655 | ); 656 | // @codeCoverageIgnoreEnd 657 | 658 | $this->maxThreads = (int)$value; 659 | 660 | $this->createAllThreads(); 661 | } 662 | } 663 | 664 | #endregion 665 | 666 | 667 | 668 | #region Internal API for usage from threads 669 | 670 | /** 671 | * Registers thread in pool 672 | * 673 | * @param Thread $thread 674 | * 675 | * @internal 676 | */ 677 | public function registerThread($thread) 678 | { 679 | $this->threads[$thread->getId()] = $thread; 680 | 681 | // @codeCoverageIgnoreStart 682 | $this->debug && $this->debug( 683 | "Thread #{$thread->getId()} registered in pool" 684 | ); 685 | // @codeCoverageIgnoreEnd 686 | } 687 | 688 | /** 689 | * Unregisters thread in pool 690 | * 691 | * @param int $threadId 692 | * 693 | * @internal 694 | */ 695 | public function unregisterThread($threadId) 696 | { 697 | if (isset($this->threads[$threadId])) { 698 | unset( 699 | $this->threads[$threadId], 700 | $this->waitingForJob[$threadId], 701 | $this->working[$threadId], 702 | $this->initializing[$threadId] 703 | ); 704 | $this->threadsCount--; 705 | 706 | // @codeCoverageIgnoreStart 707 | $this->debug && $this->debug( 708 | "Thread #{$threadId} removed from pool" 709 | ); 710 | // @codeCoverageIgnoreEnd 711 | } 712 | } 713 | 714 | 715 | /** 716 | * Sets processing result from thread 717 | * 718 | * @param int $threadId 719 | * @param mixed $result 720 | * 721 | * @throws Exception 722 | * 723 | * @internal 724 | */ 725 | public function setResultForThread($threadId, $result) 726 | { 727 | if (empty($this->working[$threadId])) { 728 | // @codeCoverageIgnoreStart 729 | // Should not be called 730 | // Break event loop to avoid freezes and other bugs 731 | $base = isset($this->threads[$threadId]) 732 | ? $this->threads[$threadId]->getEventLoop() 733 | : EventBase::getMainLoop(false); 734 | $base && $base->loopBreak(); 735 | 736 | throw new Exception("Incorrect thread for result #$threadId"); 737 | // @codeCoverageIgnoreEnd 738 | } 739 | 740 | $this->results[$threadId] = $result; 741 | $this->resultFlags[$threadId] = $threadId; 742 | 743 | // @codeCoverageIgnoreStart 744 | $this->debug && $this->debug( 745 | "Received result from thread #{$threadId}" 746 | ); 747 | // @codeCoverageIgnoreEnd 748 | } 749 | 750 | 751 | /** 752 | * Marks thread as waiting for job 753 | * 754 | * @param int $threadId 755 | * @param int $errorCode 756 | * @param string $errorMsg 757 | * 758 | * @throws Exception 759 | * 760 | * @internal 761 | */ 762 | public function markThreadWaiting($threadId, $errorCode = null, $errorMsg = null) 763 | { 764 | // Working thread 765 | if (isset($this->working[$threadId])) { 766 | if (empty($this->resultFlags[$threadId])) { 767 | $this->failures[$threadId] = array($errorCode, $errorMsg); 768 | 769 | // @codeCoverageIgnoreStart 770 | $e = new Exception(); 771 | $this->debug && $this->debug( 772 | "Received fail from thread #{$threadId}: " 773 | ."Error $errorCode - $errorMsg" 774 | ); 775 | // @codeCoverageIgnoreEnd 776 | } 777 | 778 | unset($this->working[$threadId]); 779 | $this->waitingForFetch[$threadId] = $threadId; 780 | 781 | // @codeCoverageIgnoreStart 782 | $this->debug && $this->debug( 783 | "Thread #{$threadId} is marked as waiting for results fetching" 784 | ); 785 | // @codeCoverageIgnoreEnd 786 | 787 | return; 788 | } 789 | 790 | // Skipping.. async fail 791 | else if (isset($this->waitingForFetch[$threadId]) 792 | || isset($this->waitingForJob[$threadId]) 793 | ) { 794 | return; 795 | } 796 | 797 | // Initializing thread 798 | else if (isset($this->initializing[$threadId])) { 799 | unset($this->initializing[$threadId]); 800 | $this->waitingForJob[$threadId] = $threadId; 801 | 802 | // @codeCoverageIgnoreStart 803 | $this->debug && $this->debug( 804 | "Thread #{$threadId} is marked as waiting for job" 805 | ); 806 | // @codeCoverageIgnoreEnd 807 | 808 | return; 809 | } 810 | 811 | // @codeCoverageIgnoreStart 812 | // Incorrect thread - should not be called 813 | // Break event loop to avoid freezes and other bugs 814 | $base = isset($this->threads[$threadId]) 815 | ? $this->threads[$threadId]->getEventLoop() 816 | : EventBase::getMainLoop(false); 817 | $base && $base->loopBreak(); 818 | 819 | throw new Exception( 820 | "Incorrect (not working) thread for waiting for the job #$threadId" 821 | ); 822 | // @codeCoverageIgnoreEnd 823 | } 824 | 825 | /** 826 | * Marks thread as waiting for job 827 | * 828 | * @param int $threadId 829 | * 830 | * @throws Exception 831 | * 832 | * @internal 833 | */ 834 | public function markThreadWorking($threadId) 835 | { 836 | if (empty($this->waitingForJob[$threadId])) { 837 | // @codeCoverageIgnoreStart 838 | // Should not be called 839 | // Break event loop to avoid freezes and other bugs 840 | $base = isset($this->threads[$threadId]) 841 | ? $this->threads[$threadId]->getEventLoop() 842 | : EventBase::getMainLoop(false); 843 | $base && $base->loopBreak(); 844 | 845 | throw new Exception("Incorrect thread for working #$threadId"); 846 | // @codeCoverageIgnoreEnd 847 | } 848 | 849 | unset($this->waitingForJob[$threadId]); 850 | $this->working[$threadId] = $threadId; 851 | 852 | // @codeCoverageIgnoreStart 853 | $this->debug && $this->debug( 854 | "Thread #{$threadId} is marked as working" 855 | ); 856 | // @codeCoverageIgnoreEnd 857 | } 858 | 859 | /** 860 | * Marks thread as initializing 861 | * 862 | * @param int $threadId 863 | * 864 | * @internal 865 | */ 866 | public function markThreadInitializing($threadId) 867 | { 868 | $this->initializing[$threadId] = $threadId; 869 | 870 | // @codeCoverageIgnoreStart 871 | $this->debug && $this->debug( 872 | "Thread #{$threadId} is marked as initializing" 873 | ); 874 | // @codeCoverageIgnoreEnd 875 | } 876 | 877 | #endregion 878 | 879 | 880 | 881 | /** 882 | * Debug logging 883 | * 884 | * @param string $message 885 | */ 886 | protected function debug($message) 887 | { 888 | if (!$this->debug) { 889 | return; 890 | } 891 | 892 | $time = Base::getTimeForLog(); 893 | $poolId = $this->id; 894 | $poolName = $this->poolName; 895 | $pid = posix_getpid(); 896 | $message = "{$time} [debug] [P{$poolId}.{$poolName}] " 897 | ."#{$pid}: {$message}"; 898 | 899 | if (class_exists('Aza\Kernel\Core', false) 900 | && $app = Core::$app 901 | ) { 902 | // @codeCoverageIgnoreStart 903 | // TODO: Event dispatcher call for debug message? 904 | $app->msg($message, Logger::LVL_DEBUG); 905 | } else { 906 | // @codeCoverageIgnoreEnd 907 | echo preg_replace( 908 | '~<(?:/?[a-z][a-z0-9_=;-]+|/)>~Si', '', $message 909 | ) . PHP_EOL; 910 | @ob_flush(); 911 | @flush(); 912 | } 913 | } 914 | } 915 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aza/thread", 3 | "description": "AzaThread - Anizoptera CMF simple and powerful threads emulation component for PHP (based on forks).", 4 | "keywords": [ 5 | "fork", "thread", "async", "parallel", "serialization", "multi-thread", "daemon" 6 | ], 7 | "homepage": "https://github.com/Anizoptera/AzaThread", 8 | "license": "MIT", 9 | "support": { 10 | "issues": "https://github.com/Anizoptera/AzaThread/issues" 11 | }, 12 | "authors": [ 13 | {"name": "Amal Samally", "email": "amal.samally@gmail.com", "homepage": "https://github.com/amal"}, 14 | {"name": "AzaGroup Members"} 15 | ], 16 | "require": { 17 | "php": ">=5.3.3", 18 | "aza/clibase": "~1.0", 19 | "aza/libevent": "~1.0", 20 | "aza/socket": "~1.0" 21 | }, 22 | "suggest": { 23 | "ext-posix": "Only synchronous compatibility mode will be available without it", 24 | "ext-pcntl": "Only synchronous compatibility mode will be available without it", 25 | "ext-libevent": "Only synchronous compatibility mode will be available without it" 26 | }, 27 | "autoload": { 28 | "psr-0": { 29 | "Aza\\Components\\Thread": "" 30 | } 31 | }, 32 | "target-dir": "Aza/Components/Thread", 33 | "extra": { 34 | "branch-alias": { 35 | "dev-master": "1.x-dev" 36 | } 37 | }, 38 | "minimum-stability": "dev" 39 | } 40 | -------------------------------------------------------------------------------- /docs/en/0.Index.md: -------------------------------------------------------------------------------- 1 | AzaThread documentation 2 | ======================= 3 | 4 | * [↰ back to the AzaThread overview](../../../../#azathread) 5 | 6 | 7 | --- 8 | 9 | 1. [Basic information](1.Main.md) 10 | 2. [Events transfer from thread](2.Events.md) 11 | 3. [Custom settings](3.Options.md) 12 | 4. [Hooks - more complex customization](4.Hooks.md) 13 | 5. [Handling of POSIX signals in the child and the parent process](5.Signals.md) 14 | 6. [Additions](6.Other.md) 15 | 7. [Usage examples](Examples.md) 16 | 17 | 18 | Other laguages 19 | -------------- 20 | 21 | 1. [Русский](../ru/0.Index.md) 22 | -------------------------------------------------------------------------------- /docs/en/1.Main.md: -------------------------------------------------------------------------------- 1 | Basic information 2 | ================= 3 | 4 | * [↰ back to the documentation contents](0.Index.md) 5 | * [↰ back to the AzaThread overview](../../../../#azathread) 6 | 7 | 8 | --- 9 | 10 | 11 | 1. [Basic information about the thread](#1---basic-information-about-the-thread) 12 | 2. [Simplified thread creation with closures](#2---simplified-thread-creation-with-closures) 13 | 3. [Basic information about the thread pool](#3---basic-information-about-the-thread-pool) 14 | 15 | 16 | 17 | #### 1 - Basic information about the thread 18 | 19 | Instance of class `Thread` inheritor represents "thread". It's forkes parent process and allows parallel calculations in the two processes. Communication between processes is supported by a pair of sockets, using an effective "event loop" model. 20 | 21 | Thread can also work in compatibility mode - with no real parallel, forks and "event loop". Just in case there no dependencies or for testing. To force the inclusion of this mode, set the property `Thread::$useForks` to `false`. 22 | 23 | For work you need to override the method `process` - code in it will be called asynchronously. 24 | 25 | ```php 26 | class ExampleThread extends Thread 27 | { 28 | function process() 29 | { 30 | // Code executed asynchronously 31 | } 32 | } 33 | ``` 34 | 35 | Arguments for the job can be obtained via the methods `getParam`, `getParams`. Arguments are passed as with a usual function when you call the method `run`. With the option `$argumentsMapping` you can enable the arguments mapping as in a usual function, but it slows down a bit and therefore is disabled by default. 36 | 37 | ```php 38 | class ExampleThread extends Thread 39 | { 40 | function process() 41 | { 42 | // First parameter 43 | echo $this->getParam(0); // 12 44 | // Second parameter 45 | echo $this->getParam(1); // 79 46 | // Array with all parameters 47 | $params = $this->getParams(); 48 | } 49 | } 50 | $thread = new ExampleThread(); 51 | $thread->wait()->run(12, 79); 52 | ``` 53 | 54 | Results can be sent via usual `return` and obtained by using the method `getResult`: 55 | 56 | ```php 57 | class ExampleThread extends Thread 58 | { 59 | function process() 60 | { 61 | return 123; 62 | } 63 | } 64 | $thread = new ExampleThread(); 65 | echo $thread->wait()->run()->wait()->getResult(); // 123 66 | ``` 67 | 68 | `wait` method is used to synchronize in the child and the parent process - it runs the "event loop", in which data is transferred from the parent process to the child and vice versa. 69 | 70 | You need to call method `wait` once after creating the instance of "thread" and before running the task. You can disable this behavior by enabling the option `$preforkWait` so waiting for initialization will happen automatically, **but more efficient not to do it**. 71 | 72 | More information about the configuration options for the thread can be found in [the relevant part of the documentation](3.Options.md). 73 | 74 | `getSuccess` method lets you know if last task is ended successfully or not. If the task fails, you can get error code using the `getLastErrorCode()` and the error text using `getLastErrorMsg()`. 75 | 76 | Highly recommened to explicitly free all resources after work to avoid any leaks: 77 | 78 | ```php 79 | $thread->cleanup(); 80 | ``` 81 | 82 | 83 | #### 2 - Simplified thread creation with closures 84 | 85 | You can create threads even easier - from the closures, using the `SimpleThread` class. Such threads are not preforked by default and not multitask too. You can change it via the second argument of `SimpleThread::create`. 86 | 87 | Arguments in threads-closures are always mapped as in usual functions. 88 | 89 | ```php 90 | $result = SimpleThread::create(function($arg) { 91 | return $arg; 92 | })->run(123)->wait()->getResult(); 93 | ``` 94 | 95 | Since *PHP 5.4.0* `$this` can be used in anonymous functions, as in a regular thread ([more info](http://php.net/functions.anonymous)). 96 | 97 | 98 | 99 | #### 3 - Basic information about the thread pool 100 | 101 | The thread pool can be created using the `ThreadPool` class. 102 | 103 | ```php 104 | $pool = new ThreadPool( 105 | 'ExampleThread', // The full thread class name 106 | 8 // Number of threads 107 | ); 108 | ``` 109 | 110 | Pool allows efficiently and conveniently distribute tasks among multiple threads and receive the results, events from the threads. In general, it's effective to use the number of threads as the number of processor cores, but to achieve greater efficiencies you can choose it on the specific system experimentally. 111 | 112 | Three methods are used for the basic work with a pool: `hasWaiting`, `run`, `wait`. 113 | 114 | ```php 115 | while ($pool->hasWaiting()) { 116 | $threadId = $pool->run(); 117 | // ... 118 | } 119 | if ($results = $pool->wait($failed)) { 120 | foreach ($results as $threadId => $result) { 121 | // ... 122 | } 123 | } 124 | if ($failed) { 125 | foreach ($failed as $threadId => $err) { 126 | list($errorCode, $errorMessage) = $err; 127 | // ... 128 | } 129 | } 130 | ``` 131 | 132 | `hasWaiting` tells if there are free threads in the pool. In the free thread you can start the task using the `run` method. It accepts parameters for the task and returns the ID of the thread that started processing of the task. 133 | 134 | Results can be retrieved with the `wait` method. It runs the event loop, waiting for the results and returns an array of successful results. An array of errors from tasks can be received by reference via the first argument of the `wait` method. Each error includes an error code and an explanatory text. 135 | 136 | You can identify successful and unsuccessful results by using the thread ID. 137 | 138 | Highly recommened to explicitly free all resources after work to avoid any leaks: 139 | 140 | ```php 141 | $pool->cleanup(); 142 | ``` 143 | 144 | A complete example of using the pool with error handling can be found [among other examples](Examples.md). 145 | -------------------------------------------------------------------------------- /docs/en/2.Events.md: -------------------------------------------------------------------------------- 1 | Events transfer from thread 2 | =========================== 3 | 4 | * [↰ back to the documentation contents](0.Index.md) 5 | * [↰ back to the AzaThread overview](../../../../#azathread) 6 | 7 | 8 | --- 9 | 10 | 11 | 1. [Events from the thread](#1---events-from-the-thread) 12 | 2. [Events from the thread pool](#2---events-from-the-thread-pool) 13 | 14 | 15 | 16 | #### 1 - Events from the thread 17 | 18 | During task execution "thread" can report something to parent. To do this, each thread is the event dispatcher. 19 | 20 | Sending event can be made with the `trigger` method. You can send various data with the event. Such as the status of work, or any other information. 21 | 22 | Subscribe to events in the parent process with the `bind` method. You can also specify an additional argument with the callback and the event name when subscribing. Its value will be passed to the callback as the third argument each time it triggers. 23 | 24 | ```php 25 | class ExampleThread extends Thread 26 | { 27 | const EV_NAME = 'example_name'; 28 | function process() 29 | { 30 | // ... 31 | $this->trigger(self::EV_NAME, $event_data); 32 | // ... 33 | } 34 | } 35 | $thread = new ExampleThread(); 36 | $thread->bind(ExampleThread::EV_NAME, function($event_name, $event_data, $additional_arg) { 37 | // ... 38 | }, $additional_arg); 39 | $thread->wait()->run()->wait(); 40 | ``` 41 | 42 | An example of events generation from the "thread" can be found [among other examples](Examples.md). 43 | 44 | 45 | 46 | #### 2 - Events from the thread pool 47 | 48 | Receiving events from from the thread pool is as simple as a from the single thread. Just subscribe on the pool. In this case, you will have a second argument for the callback - ID of the thread that sent the event. 49 | 50 | ```php 51 | $cb = function($event_name, $threadId, $event_data, $additional_arg) { 52 | // ... 53 | }; 54 | $pool->bind(ExampleThread::EV_NAME, $cb, $additional_arg); 55 | ``` 56 | 57 | Within the thread method call is the same - `trigger`. 58 | 59 | ```php 60 | $this->trigger(self::EV_NAME, $event_data); 61 | ``` 62 | -------------------------------------------------------------------------------- /docs/en/3.Options.md: -------------------------------------------------------------------------------- 1 | Custom settings 2 | =============== 3 | 4 | * [↰ back to the documentation contents](0.Index.md) 5 | * [↰ back to the AzaThread overview](../../../../#azathread) 6 | 7 | 8 | --- 9 | 10 | 11 | 1. [General information](#1---general-information) 12 | 2. [Thread settings](#2---thread-settings) 13 | * [$multitask](#multitask) 14 | * [$listenMasterSignals](#listenmastersignals) 15 | * [$prefork](#prefork) 16 | * [$preforkWait](#preforkwait) 17 | * [$argumentsMapping](#argumentsmapping) 18 | * [$timeoutMasterInitWait](#timeoutmasterinitwait) 19 | * [$timeoutMasterResultWait](#timeoutmasterresultwait) 20 | * [$timeoutWorkerJobWait](#timeoutworkerjobwait) 21 | * [$intervalWorkerMasterChecks](#intervalworkermasterchecks) 22 | 3. [Static configuration](#3---static-configuration) 23 | * [Thread::$ipcDataMode](#threadipcdatamode) 24 | * [Thread::$useForks](#threaduseforks) 25 | 26 | 27 | 28 | #### 1 - General information 29 | 30 | AzaThread flexibly adjusts for any of your tasks. There are two static parameters which affects all the threads and many parameters affecting each individual thread. 31 | 32 | The standard way to specify settings - override a class property: 33 | 34 | ```php 35 | class ExampleThread extends Thread 36 | { 37 | protected $prefork = false; // Turn off preforking 38 | function process() 39 | { 40 | // ... 41 | } 42 | } 43 | ``` 44 | 45 | The second variant - is to send an array of settings. This method is intended for closures, but can be used with regular classes. 46 | 47 | ```php 48 | $thread = SimpleThread::create(function() { 49 | // ... 50 | }, array( 51 | 'timeoutMasterResultWait' => 15, // Set the timeout for waiting for the result in 15 seconds 52 | )); 53 | ``` 54 | 55 | ```php 56 | $thread = new ExampleThread(null, null, null, array( 57 | 'timeoutMasterResultWait' => 15, // Set the timeout for waiting for the result in 15 seconds 58 | )); 59 | ``` 60 | 61 | Most options are not checked at the processing time, so changing property values ​​after creating the "thread" instance does not make sense. 62 | 63 | 64 | 65 | #### 2 - Thread settings 66 | 67 | 68 | ###### $multitask 69 | 70 | Default value -`true`. 71 | 72 | Flag. Enables waiting for next tasks in thread. If disabled, then after one task child process dies. 73 | 74 | Preforked threads (`$prefork = true`) are always multitask. 75 | 76 | 77 | ###### $listenMasterSignals 78 | 79 | Default value -`true`. 80 | 81 | Flag. Enables listening for all POSIX signals in parent process. **SIGCHLD** is always listened - it is required for normal functioning. 82 | 83 | More information about signals processing in the both parent and child processes can be read in [the relevant part of the documentation](5.Signals.md). 84 | 85 | 86 | ###### $prefork 87 | 88 | Default value -`true`. 89 | 90 | Flag. Enables pre-forking, to avoid wasting resources later. The process is forked directly at the creation of the instance. This allows more efficient initialization (especially in pools). 91 | 92 | Preforked threads are always multitask. 93 | 94 | 95 | ###### $preforkWait 96 | 97 | Default value -`false`. 98 | 99 | Flag. Enables forced wait for the pre-forking child. This allows you to save one call to `wait` method before running the task, but it takes away all the effectiveness of pre-provisioning. So it is not recommended to use. 100 | 101 | 102 | ###### $argumentsMapping 103 | 104 | Default value -`false`. 105 | 106 | Flag. Enables mapping of arguments for the `process` method. So you can get arguments as in usual function, not only through the `getParam`/`getParams`: 107 | 108 | ```php 109 | class ExampleThread extends Thread 110 | { 111 | protected $argumentsMapping = true; 112 | function process($arg1, $arg2) 113 | { 114 | echo $arg1; // 12 115 | echo $arg2; // 79 116 | } 117 | } 118 | $thread = new ExampleThread(); 119 | $thread->wait()->run(12, 79); 120 | ``` 121 | 122 | Creates a little performance overhead, so disabled by default. 123 | 124 | 125 | ###### $timeoutMasterInitWait 126 | 127 | Default value -`3`. 128 | 129 | Timeout. Maximum timeout for master to wait for worker initialization (prefork) (in seconds, can be fractional). 130 | 131 | Set to a negative value (`-1`) to disable. 132 | 133 | 134 | ###### $timeoutMasterResultWait 135 | 136 | Default value -`5`. 137 | 138 | Timeout. Maximum timeout for master to wait for the job results (in seconds, can be fractional). 139 | 140 | Set to a negative value (`-1`) to disable. 141 | 142 | 143 | ###### $timeoutWorkerJobWait 144 | 145 | Default value -`600`. 146 | 147 | Timeout. Maximum timeout for worker to wait for the new job (in seconds, can be fractional). After it spawned child will die. 148 | 149 | Set to a negative value (`-1`) to disable. 150 | 151 | 152 | ###### $intervalWorkerMasterChecks 153 | 154 | Default value -`5`. 155 | 156 | Interval. Interval for worker to check master process (in seconds, can be fractional). If it's not successfull child will die. 157 | 158 | Can not be turned off. If incorrect then default value will be used. 159 | 160 | 161 | 162 | #### 3 - Static configuration 163 | 164 | 165 | ###### Thread::$ipcDataMode 166 | 167 | Serialization mode for transferring data between processes. By default, the php serialization is used. If [igbinary](http://pecl.php.net/package/igbinary) extension is available, then it is automatically used. Not recommended to change the value manually. 168 | 169 | 170 | ###### Thread::$useForks 171 | 172 | Availability flag for the full feature mode. Automatically set to `true`, if all dependencies are available. Если установлено в `` ложной, то «нити» будет использовать синхронный режим совместимости. You can turn it on, if you want to test the functionality in a simplified synchronous mode without forking. 173 | -------------------------------------------------------------------------------- /docs/en/4.Hooks.md: -------------------------------------------------------------------------------- 1 | Hooks - more complex customization 2 | ================================== 3 | 4 | * [↰ back to the documentation contents](0.Index.md) 5 | * [↰ back to the AzaThread overview](../../../../#azathread) 6 | 7 | 8 | --- 9 | 10 | 11 | 1. [General information](#1---general-information) 12 | * [onLoad](#onload) 13 | * [onFork](#onfork) 14 | * [onShutdown](#onshutdown) 15 | * [onCleanup](#oncleanup) 16 | 17 | 18 | 19 | #### 1 - General information 20 | 21 | You can adjust the "thread" even more flexible with "hooks". It is a few methods in AzaThread, which are called in a variety of situations. And you can override them to perform any special functionality. 22 | 23 | ```php 24 | class ExampleThread extends Thread 25 | { 26 | function process() 27 | { 28 | // ... 29 | } 30 | 31 | protected function onLoad() 32 | { 33 | // ... 34 | } 35 | } 36 | ``` 37 | 38 | 39 | 40 | ###### onLoad 41 | 42 | Hook called after thread initialization, but BEFORE forking! 43 | 44 | 45 | ###### onFork 46 | 47 | Hook called after thread forking (only in child process). Use this if you need to initialize something in child process and do it only once. 48 | 49 | It's already called after initialization in synchronous fallback mode! 50 | 51 | 52 | ###### onShutdown 53 | 54 | Hook called before shutdown (only in child process). Use it if you need to do something before the end of the child process. 55 | 56 | 57 | ###### onCleanup 58 | 59 | Called in child and parent processes during the full resources cleanup. Use it if you need to close/cleanup something. 60 | -------------------------------------------------------------------------------- /docs/en/5.Signals.md: -------------------------------------------------------------------------------- 1 | Handling of POSIX signals in the child and the parent process 2 | ============================================================= 3 | 4 | * [↰ back to the documentation contents](0.Index.md) 5 | * [↰ back to the AzaThread overview](../../../../#azathread) 6 | 7 | 8 | --- 9 | 10 | 11 | 1. [General information](#1---general-information) 12 | 2. [Sending signals](#2---sending-signals) 13 | 14 | 15 | 16 | #### 1 - General information 17 | 18 | With AzaThread you can handle POSIX signals in the parent and the child processes. In the child process all available signals are always listened. In parent, this is done only when the option `$listenMasterSignals` is enabled (by default). 19 | 20 | To handle signal it's enough to declare a method that will be called when it is received. For the child process method name is the name of the signal (case insensitive). The first argument of this method will be the number - the signal code. 21 | 22 | ```php 23 | class ExampleThread extends Thread 24 | { 25 | function process() 26 | { 27 | // ... 28 | } 29 | 30 | /** 31 | * SIGUSR1 handler for the child process 32 | * 33 | * @param int $signo 34 | */ 35 | protected function sigUsr1($signo) 36 | { 37 | // ... 38 | } 39 | } 40 | ``` 41 | 42 | 43 | All the same for the parent process, only the method must be static and prefixed with **"m"**. 44 | 45 | ```php 46 | class ExampleThread extends Thread 47 | { 48 | function process() 49 | { 50 | // ... 51 | } 52 | 53 | /** 54 | * SIGUSR2 handler for the parent process 55 | * 56 | * @param int $signo 57 | */ 58 | protected static function mSigUsr2($signo) 59 | { 60 | // ... 61 | } 62 | } 63 | ``` 64 | 65 | 66 | 67 | #### 2 - Sending signals 68 | 69 | Send signals is as simple as receiving. To do this, there are two public methods: `sendSignalToParent`, `sendSignalToChild`. Which send a signal to the parent and child process respectively. 70 | 71 | ```php 72 | $thread->sendSignalToChild(SIGUSR1); 73 | ``` 74 | 75 | ```php 76 | $this->sendSignalToParent(SIGUSR2); 77 | ``` 78 | -------------------------------------------------------------------------------- /docs/en/6.Other.md: -------------------------------------------------------------------------------- 1 | Additions 2 | ========= 3 | 4 | * [↰ back to the documentation contents](0.Index.md) 5 | * [↰ back to the AzaThread overview](../../../../#azathread) 6 | 7 | 8 | --- 9 | 10 | 11 | 1. [Set the name of the process](#1---set-the-name-of-the-process) 12 | 2. [Additionally](#2---additionally) 13 | 14 | 15 | 16 | #### 1 - Set the name of the process 17 | 18 | AzaThread allows to set the name of the process for child processes (if it is possible). To do this, you need to pass a string with the name as an argument to the thread or to the pool. 19 | 20 | ```php 21 | $processName = 'worker'; 22 | new ExampleThread($processName); 23 | ``` 24 | 25 | ```php 26 | $processName = 'worker'; 27 | new ThreadPool($threadClass, $numberOfThreads, $processName); 28 | ``` 29 | 30 | 31 | 32 | #### 2 - Additionally 33 | 34 | More information about the AzaThread can be found in the source code - it is well commented and completely covered by the tests. In the tests can be seen more examples of usage. 35 | 36 | Also, you can better understand the functioning of AzaThread, enabling debugging. It launches very detailed logging of all that happens. Enable easy - just set the argument `$debug` to `true` for thread or pool of threads. 37 | 38 | ```php 39 | $debug = true; 40 | new ExampleThread(null, null, $debug); 41 | ``` 42 | 43 | ```php 44 | $debug = true; 45 | new ThreadPool($threadClass, $numberOfThreads, null, null, $debug); 46 | ``` 47 | 48 | ```php 49 | $debug = true; 50 | $thread = SimpleThread::create(function() { 51 | // ... 52 | }, null, $debug); 53 | ``` 54 | 55 | 56 | If you find a bug or have suggestions to improve AzaThread, please do not hesitate to open the pool requests and issues in the bug tracker. 57 | -------------------------------------------------------------------------------- /docs/en/Examples.md: -------------------------------------------------------------------------------- 1 | Usage examples 2 | ============== 3 | 4 | * [↰ back to the documentation contents](0.Index.md) 5 | * [↰ back to the AzaThread overview](../../../../#azathread) 6 | 7 | 8 | 9 | --- 10 | 11 | 12 | 13 | 1. [Simply run processing asynchronously](#example-1---simply-run-processing-asynchronously) 14 | 2. [Send argument and receive result of processing](#example-2---send-argument-and-receive-result-of-processing) 15 | 3. [Triggering events from thread](#example-3---triggering-events-from-thread) 16 | 4. [Use pool with 8 threads](#example-4---use-pool-with-8-threads) 17 | 5. [Thread closure](#example-5---thread-closure) 18 | 19 | 20 | #### Example #1 - Simply run processing asynchronously 21 | 22 | ```php 23 | class ExampleThread extends Thread 24 | { 25 | function process() 26 | { 27 | // Some work here 28 | } 29 | } 30 | 31 | $thread = new ExampleThread(); 32 | $thread->wait()->run(); 33 | ``` 34 | 35 | 36 | #### Example #2 - Send argument and receive result of processing 37 | 38 | ```php 39 | class ExampleThread extends Thread 40 | { 41 | function process() 42 | { 43 | return $this->getParam(0); 44 | } 45 | } 46 | 47 | $thread = new ExampleThread(); 48 | $result = $thread->wait()->run(123)->wait()->getResult(); 49 | 50 | // After work it's strongly recommended to clean 51 | // resources obviously to avoid leaks 52 | $thread->cleanup(); 53 | ``` 54 | 55 | 56 | #### Example #3 - Triggering events from thread 57 | 58 | ```php 59 | class ExampleThread extends Thread 60 | { 61 | const EV_PROCESS = 'process'; 62 | 63 | function process() 64 | { 65 | $events = $this->getParam(0); 66 | for ($i = 0; $i < $events; $i++) { 67 | $event_data = $i; 68 | $this->trigger(self::EV_PROCESS, $event_data); 69 | } 70 | } 71 | } 72 | 73 | $thread = new ExampleThread(); 74 | 75 | // Additional argument. 76 | $additionalArgument = 123; 77 | 78 | $thread->bind(ExampleThread::EV_PROCESS, function($event_name, $event_data, $additional_arg) { 79 | // Event handling 80 | }, $additionalArgument); 81 | 82 | $events = 10; // number of events to trigger 83 | 84 | // You can override preforkWait property 85 | // to TRUE to not wait thread at first time manually. 86 | // In this case, waiting for initialization will happen 87 | // automatically, but more efficient not to do it. 88 | $thread->wait(); 89 | 90 | $thread->run($events)->wait(); 91 | 92 | // After work it's strongly recommended to clean 93 | // resources obviously to avoid leaks 94 | $thread->cleanup(); 95 | ``` 96 | 97 | 98 | #### Example #4 - Use pool with 8 threads 99 | 100 | ```php 101 | $threads = 8 // Number of threads 102 | $pool = new ThreadPool('ExampleThread', $threads); 103 | 104 | $num = 25; // Number of tasks 105 | $left = $num; // Remaining number of tasks 106 | 107 | do { 108 | // If we still have tasks to perform 109 | // And the pool has waiting threads 110 | while ($left > 0 && $pool->hasWaiting()) { 111 | // You get thread id after start 112 | $threadId = $pool->run(); 113 | $left--; 114 | } 115 | if ($results = $pool->wait($failed)) { 116 | foreach ($results as $threadId => $result) { 117 | // Successfully completed task 118 | // Result can be identified 119 | // with thread id ($threadId) 120 | $num--; 121 | } 122 | } 123 | if ($failed) { 124 | // Error handling. 125 | // The work is completed unsuccessfully 126 | // if the child process has died at run time or 127 | // work timeout exceeded. 128 | foreach ($failed as $threadId => $err) { 129 | list($errorCode, $errorMessage) = $err; 130 | $left++; 131 | } 132 | } 133 | } while ($num > 0); 134 | 135 | // Terminating all child processes. Cleanup of resources used by the pool. 136 | $pool->cleanup(); 137 | 138 | // After work it's strongly recommended to clean 139 | // resources obviously to avoid leaks 140 | $pool->cleanup(); 141 | ``` 142 | 143 | 144 | #### Example #5 - Thread closure 145 | 146 | You can use simple threads crating with closures. Such threads are not preforked by default and not multitask too. You can change it via the second argument of `SimpleThread::create`. 147 | 148 | ```php 149 | $result = SimpleThread::create(function($arg) { 150 | return $arg; 151 | })->run(123)->wait()->getResult(); 152 | ``` 153 | 154 | 155 | 156 | --- 157 | 158 | 159 | 160 | Other examples can be seen in the file [examples/example.php](../examples/example.php) and in unit test [Tests/ThreadTest.php](../Tests/ThreadTest.php). 161 | 162 | You can also run the performance tests, choose the number of threads and pick the best settings for your system configuration by using [examples/speed_test.php](../examples/speed_test.php). 163 | -------------------------------------------------------------------------------- /docs/ru/0.Index.md: -------------------------------------------------------------------------------- 1 | AzaThread документация 2 | ====================== 3 | 4 | * [↰ назад к общей информации об AzaThread](../../../../#azathread) 5 | 6 | 7 | --- 8 | 9 | 1. [Основная информация](1.Main.md) 10 | 2. [Передача событий из потока](2.Events.md) 11 | 3. [Настраиваемые параметры](3.Options.md) 12 | 4. [Хуки - более сложная кастомизация](4.Hooks.md) 13 | 5. [Обработка POSIX сигналов в дочернем и родительском процессе](5.Signals.md) 14 | 6. [Дополнительно](6.Other.md) 15 | 7. [Примеры использования](Examples.md) 16 | 17 | 18 | Другие языки 19 | ------------ 20 | 21 | 1. [English](../en/0.Index.md) 22 | -------------------------------------------------------------------------------- /docs/ru/1.Main.md: -------------------------------------------------------------------------------- 1 | Основная информация 2 | =================== 3 | 4 | * [↰ назад к оглавлению документации](0.Index.md) 5 | * [↰ назад к общей информации об AzaThread](../../../../#azathread) 6 | 7 | 8 | --- 9 | 10 | 11 | 1. [Базовая информация о потоке](#1------) 12 | 2. [Упрощенное создание потока с замыканиями](#2-------) 13 | 3. [Базовая информация о пуле потоков](#3-------) 14 | 15 | 16 | 17 | #### 1 - Базовая информация о потоке 18 | 19 | "Поток" представляет из себя наследника класса `Thread`. Он разветвляет (форкает) процесс-родитель и позволяет выполнять различные вычисления параллельно в двух процессах. Связь между процессами поддерживается с помощью пары сокетов, используя эффективную модель "event loop". 20 | 21 | Поток также может работать в режиме совместимости - без реальной параллельности, форков и "event loop". На случай если нет каких либо зависимостей или для тестирования. Для принудительного включения этого режима установите свойство `Thread::$useForks` в `false`. 22 | 23 | Для работы вам нужно переопределить метод `process` - код в нем, как раз и будет вызываться асинхронно. 24 | 25 | ```php 26 | class ExampleThread extends Thread 27 | { 28 | function process() 29 | { 30 | // Код, выполняемый асинхронно 31 | } 32 | } 33 | ``` 34 | 35 | Аргументы для задачи можно получать с помощью методов `getParam`, `getParams`. Передаются аргументы как в обычную функцию при вызове метода `run`. С помощью опции `$argumentsMapping` можно включить получение аргументов, как в обычной функции, но это немного замедляет работу и поэтому отключено по умолчанию. 36 | 37 | ```php 38 | class ExampleThread extends Thread 39 | { 40 | function process() 41 | { 42 | // Первый параметр 43 | echo $this->getParam(0); // 12 44 | // Второй параметр 45 | echo $this->getParam(1); // 79 46 | // Массив со всеми параметрами 47 | $params = $this->getParams(); 48 | } 49 | } 50 | $thread = new ExampleThread(); 51 | $thread->wait()->run(12, 79); 52 | ``` 53 | 54 | Результаты выполнения задачи могут быть отправлены с помощью обычного `return` и получены с помощью метода `getResult`: 55 | 56 | ```php 57 | class ExampleThread extends Thread 58 | { 59 | function process() 60 | { 61 | return 123; 62 | } 63 | } 64 | $thread = new ExampleThread(); 65 | echo $thread->wait()->run()->wait()->getResult(); // 123 66 | ``` 67 | 68 | Метод `wait` используется для синхронизации в дочернем и родительском процессе - он запускает "event loop", в котором происходит передача информации из родительского процесса в дочерний и обратно. 69 | 70 | После создания инстанса "потока" перед запуском задачи нужно один раз вызывать метод `wait`, чтобы дождаться инициализации потока. Можно отключить такое поведение с помощью включения опции `$preforkWait` ожидание будет происходить автоматически и вызывать метод `wait` перед первым вызовом метода `run` будет не нужно. **Но эффективнее этого не делать.** 71 | 72 | Подробнее про варианты настройки потока можно прочитать в [соответствующей части документации](3.Options.md). 73 | 74 | Метод `getSuccess` позволяет узнать, успешно или нет закончилось последнее выполнение задачи. Если задача не выполнена, то можно узнать код ошибки с помощью `getLastErrorCode()` и текст ошибки с помощью `getLastErrorMsg()`. 75 | 76 | После работы настоятельно рекоммендуется явно освободить все ресурсы, чтобы избежать различных утечек: 77 | 78 | ```php 79 | $thread->cleanup(); 80 | ``` 81 | 82 | 83 | #### 2 - Упрощенное создание потока с замыканиями 84 | 85 | Вы можете еще проще создавать потоки - из замыканий, с помощью класса `SimpleThread`. Такие потоки, по умолчанию, не ответвляют сразу дочерний процесс и не многозадачны (дочерний процесс умирает после каждой задачи). Это может быть изменено с помощью второго аргумента в `SimpleThread::create`. 86 | 87 | Аргументы в потоках-замыканиях всегда можно получать как в обычной функции. 88 | 89 | ```php 90 | $result = SimpleThread::create(function($arg) { 91 | return $arg; 92 | })->run(123)->wait()->getResult(); 93 | ``` 94 | 95 | Начиная с *PHP 5.4.0* внутри замыкания можно использовать все стандартные методы потока через `$this`, как и в обычном классе-потоке ([больше информации](http://php.net/functions.anonymous)). 96 | 97 | 98 | 99 | #### 3 - Базовая информация о пуле потоков 100 | 101 | Пул потоков можно создать с помощью класса `ThreadPool`. 102 | 103 | ```php 104 | $pool = new ThreadPool( 105 | 'ExampleThread', // Полное имя класса потока 106 | 8 // Число потоков 107 | ); 108 | ``` 109 | 110 | Пул позволяет эффективно и удобно распределять задачи между несколькими потоками, получать результаты выполнения, события из потоков и.т.п. В общем случае эффективно использовать число потоков по числу ядер процессора, но для достижения большей эффективности можно подобрать число уже на конкретной системе экспериментально. 111 | 112 | Для основной работы с пулом используются три метода: `hasWaiting`, `run`, `wait`. 113 | 114 | ```php 115 | while ($pool->hasWaiting()) { 116 | $threadId = $pool->run(); 117 | // ... 118 | } 119 | if ($results = $pool->wait($failed)) { 120 | foreach ($results as $threadId => $result) { 121 | // ... 122 | } 123 | } 124 | if ($failed) { 125 | foreach ($failed as $threadId => $err) { 126 | list($errorCode, $errorMessage) = $err; 127 | // ... 128 | } 129 | } 130 | ``` 131 | 132 | `hasWaiting` сообщает есть ли в пуле свободные потоки. Если есть, то в свободном потоке можно запустить задачу с помощью метода `run`. Он принимает аргументы для задачи и возвращает ID потока, который начал задачу выполнять. 133 | 134 | Результаты выполнения можно получить с помощью метода `wait`. Он запускает "event loop", ожидая результатов и возвращает массив успешных результатов. Массив ошибок при выполнении задач можно получить по ссылке через первый аргумент метода `wait`. Каждая ошибка включает код ошибки и поясняющий текст. 135 | 136 | Идентифицировать успешные и неудачные результаты можно с помощью ID потока. 137 | 138 | После работы настоятельно рекоммендуется явно освободить все ресурсы, чтобы избежать различных утечек: 139 | 140 | ```php 141 | $pool->cleanup(); 142 | ``` 143 | 144 | Полный пример использования пула с обработкой ошибок можно посмотреть [среди остальных примеров](Examples.md). 145 | -------------------------------------------------------------------------------- /docs/ru/2.Events.md: -------------------------------------------------------------------------------- 1 | Передача событий из потока 2 | ========================== 3 | 4 | * [↰ назад к оглавлению документации](0.Index.md) 5 | * [↰ назад к общей информации об AzaThread](../../../../#azathread) 6 | 7 | 8 | --- 9 | 10 | 11 | 1. [События из потока](#1-----) 12 | 2. [События из пула потоков](#2------) 13 | 14 | 15 | 16 | #### 1 - События из потока 17 | 18 | Во время выполнения задачи "поток" может сообщать что либо в родительский процесс. Для этого каждый поток является диспетчером событий. 19 | 20 | Отправка события выполняется с помощью метода `trigger`. Вместе с событием можно передать различные данные. Например состояние работы или любую другую информацию. 21 | 22 | Подписаться на события в родительском процессе можно с помощью метода `bind`. При подписке кроме коллбэка и имени события можно также указать дополнительный аргумент. Его значение будет передаваться в коллбэк третьим аргументом при каждом срабатывании. 23 | 24 | ```php 25 | class ExampleThread extends Thread 26 | { 27 | const EV_NAME = 'example_name'; 28 | function process() 29 | { 30 | // ... 31 | $this->trigger(self::EV_NAME, $event_data); 32 | // ... 33 | } 34 | } 35 | $thread = new ExampleThread(); 36 | $thread->bind(ExampleThread::EV_NAME, function($event_name, $event_data, $additional_arg) { 37 | // ... 38 | }, $additional_arg); 39 | $thread->wait()->run()->wait(); 40 | ``` 41 | 42 | Пример генерирования событий из потока можно посмотреть [среди остальных примеров](Examples.md). 43 | 44 | 45 | 46 | #### 2 - События из пула потоков 47 | 48 | События из пула потоков получать также просто, как и из одного потока. Просто подписывать нужно на пуле. В этом случае добавляется второй аргумент для колбэка - ID потока, отправившего событие. 49 | 50 | ```php 51 | $cb = function($event_name, $threadId, $event_data, $additional_arg) { 52 | // ... 53 | }; 54 | $pool->bind(ExampleThread::EV_NAME, $cb, $additional_arg); 55 | ``` 56 | 57 | Внутри потока вызывается все тот же `trigger`. 58 | 59 | ```php 60 | $this->trigger(self::EV_NAME, $event_data); 61 | ``` 62 | -------------------------------------------------------------------------------- /docs/ru/3.Options.md: -------------------------------------------------------------------------------- 1 | Настраиваемые параметры 2 | ======================= 3 | 4 | * [↰ назад к оглавлению документации](0.Index.md) 5 | * [↰ назад к общей информации об AzaThread](../../../../#azathread) 6 | 7 | 8 | --- 9 | 10 | 11 | 1. [Общая информация](#1----) 12 | 2. [Настройки потока](#2----) 13 | * [$multitask](#multitask) 14 | * [$listenMasterSignals](#listenmastersignals) 15 | * [$prefork](#prefork) 16 | * [$preforkWait](#preforkwait) 17 | * [$argumentsMapping](#argumentsmapping) 18 | * [$timeoutMasterInitWait](#timeoutmasterinitwait) 19 | * [$timeoutMasterResultWait](#timeoutmasterresultwait) 20 | * [$timeoutWorkerJobWait](#timeoutworkerjobwait) 21 | * [$intervalWorkerMasterChecks](#intervalworkermasterchecks) 22 | 3. [Статические настройки](#3----) 23 | * [Thread::$ipcDataMode](#threadipcdatamode) 24 | * [Thread::$useForks](#threaduseforks) 25 | 26 | 27 | 28 | #### 1 - Общая информация 29 | 30 | AzaThread гибко настраивается под любые ваши задачи. Есть два статических параметра, влиящих на все потоки и много параметров влияющих на каждый конкретный поток. 31 | 32 | Стандартный способ задавать настройки - переопределить свойство класса: 33 | 34 | ```php 35 | class ExampleThread extends Thread 36 | { 37 | protected $prefork = false; // Выключаем предварительную инициализацию 38 | function process() 39 | { 40 | // ... 41 | } 42 | } 43 | ``` 44 | 45 | Второй вариант - передача настроек массивом. Этот способ предназначен для замыканий, но может использоваться и с обычными классами. 46 | 47 | ```php 48 | $thread = SimpleThread::create(function() { 49 | // ... 50 | }, array( 51 | 'timeoutMasterResultWait' => 15, // Устанавливаем таймаут на ожидание результата в 15 секунд 52 | )); 53 | ``` 54 | 55 | ```php 56 | $thread = new ExampleThread(null, null, null, array( 57 | 'timeoutMasterResultWait' => 15, // Устанавливаем таймаут на ожидание результата в 15 секунд 58 | )); 59 | ``` 60 | 61 | Большинство опций никак не проверяются в процессе работы потока, поэтому изменять значения свойств после создания инстанса потока не имеет смысла. 62 | 63 | 64 | 65 | #### 2 - Настройки потока 66 | 67 | 68 | ###### $multitask 69 | 70 | Значение по умолчанию - `true`. 71 | 72 | Флаг. Включает ожидание потоком следующих задач. Если отключено, то после выполнения одной задачи дочерний процесс умирает. 73 | 74 | Потоки с предварительной инициализацией (`$prefork = true`) всегда многозадачны. 75 | 76 | 77 | ###### $listenMasterSignals 78 | 79 | Значение по умолчанию - `true`. 80 | 81 | Флаг. Включает в родительском процессе ожидание всех POSIX сигналов. **SIGCHLD** ожидается в любом случае - этот сигнал необходим для нормального функционирования. 82 | 83 | Подробнее про обработку полученных как родительским, так и дочерним процессом сигналов можно прочитать в [соответствующей части документации](5.Signals.md). 84 | 85 | 86 | ###### $prefork 87 | 88 | Значение по умолчанию - `true`. 89 | 90 | Флаг. Включает предварительную инициализацию "потока". Дочерний процесс ответвляется сразу во время создания инстанса. Это позволяет проводить инициализацию более эффективно (особенно в пулах). 91 | 92 | Потоки с предварительной инициализацией всегда многозадачны. 93 | 94 | 95 | ###### $preforkWait 96 | 97 | Значение по умолчанию - `false`. 98 | 99 | Флаг. Включает принудительной ожидание предварительной инициализации. Это позволяет не делать один вызов метода `wait` перед запуском задач, но при этом убирает весь выйгрыш эффективности от предварительной инициализации. Так что не рекоммендуется к использованию. 100 | 101 | 102 | ###### $argumentsMapping 103 | 104 | Значение по умолчанию - `false`. 105 | 106 | Флаг. Включает маппинг аргументов в метод `process`. Что позволяет получать аргументы не через `getParam`/`getParams`, а как в обычной функции: 107 | 108 | ```php 109 | class ExampleThread extends Thread 110 | { 111 | protected $argumentsMapping = true; 112 | function process($arg1, $arg2) 113 | { 114 | echo $arg1; // 12 115 | echo $arg2; // 79 116 | } 117 | } 118 | $thread = new ExampleThread(); 119 | $thread->wait()->run(12, 79); 120 | ``` 121 | 122 | Это слегка замедляет работу и поэтому отключено по умолчанию. 123 | 124 | 125 | ###### $timeoutMasterInitWait 126 | 127 | Значение по умолчанию - `3`. 128 | 129 | Таймаут. Устанавливает максимальное время ожидания инициализации дочернего процесса в секундах (может иметь дробное значение). 130 | 131 | Установите в отрицательное значение (`-1`), чтобы отключить. 132 | 133 | 134 | ###### $timeoutMasterResultWait 135 | 136 | Значение по умолчанию - `5`. 137 | 138 | Таймаут. Устанавливает максимальное время ожидания результата в родительском процессе в секундах (может иметь дробное значение). 139 | 140 | Установите в отрицательное значение (`-1`), чтобы отключить. 141 | 142 | 143 | ###### $timeoutWorkerJobWait 144 | 145 | Значение по умолчанию - `600`. 146 | 147 | Таймаут. Устанавливает максимальное время ожидания новой задачи в дочернем процессе в секундах (может иметь дробное значение). Если за это время новая задача не будет получена, то дочерний процесс умирает. 148 | 149 | Установите в отрицательное значение (`-1`), чтобы отключить. 150 | 151 | 152 | ###### $intervalWorkerMasterChecks 153 | 154 | Значение по умолчанию - `5`. 155 | 156 | Интервал. Устанавливает интервал в секундах (может иметь дробное значение), по истечении которого дочерний процесс проверяет, что родительский процесс жив. Если проверка неуспешна, то дочерний процесс умирает. 157 | 158 | Не может быть отключен. При некорректном значении будет использоваться значение по умолчанию. 159 | 160 | 161 | 162 | #### 3 - Статические настройки 163 | 164 | 165 | ###### Thread::$ipcDataMode 166 | 167 | Режим сериализации данных для передачи между процессами. По умолчанию используется стандартная сериализация php. Если доступно расширение [igbinary](http://pecl.php.net/package/igbinary), то автоматически используется оно. Не рекоммендуется менять значение вручную. 168 | 169 | 170 | ###### Thread::$useForks 171 | 172 | Флаг доступности полноценного режима работы. Автоматически выставляется в `true`, если доступны все зависимости. Если выставлен в `false`, то "потоки" используют синхронный режим совместимости. Вы можете включить его специально, если необходимо тестирования функционала в упрощенном синхронном режиме без разветвления. 173 | -------------------------------------------------------------------------------- /docs/ru/4.Hooks.md: -------------------------------------------------------------------------------- 1 | Хуки - более сложная кастомизация 2 | ================================= 3 | 4 | * [↰ назад к оглавлению документации](0.Index.md) 5 | * [↰ назад к общей информации об AzaThread](../../../../#azathread) 6 | 7 | 8 | --- 9 | 10 | 11 | 1. [Общая информация](#1----) 12 | * [onLoad](#onload) 13 | * [onFork](#onfork) 14 | * [onShutdown](#onshutdown) 15 | * [onCleanup](#oncleanup) 16 | 17 | 18 | 19 | #### 1 - Общая информация 20 | 21 | Вы можете настраивать "поток" еще более гибко с помощью "хуков". Это несколько методов в AzaThread, которые вызываются в различных ситуациях. И вы можете переопределить их для выполнения какого либо специального функционала. 22 | 23 | ```php 24 | class ExampleThread extends Thread 25 | { 26 | function process() 27 | { 28 | // ... 29 | } 30 | 31 | protected function onLoad() 32 | { 33 | // ... 34 | } 35 | } 36 | ``` 37 | 38 | 39 | 40 | ###### onLoad 41 | 42 | Этот хук вызывается в конструкторе после инициализации потока, но ДО ответвления дочернего процесса! 43 | 44 | 45 | ###### onFork 46 | 47 | Хук вызывается в дочернем процессе сразу после ответвления от родительского. Используйте, если вам необходимо что либо инициализировать в дочернем процессе и сделать это только один раз. 48 | 49 | Также вызывается в синхронном режиме совместимости в конце инициализации! 50 | 51 | 52 | ###### onShutdown 53 | 54 | Вызывается только в дочернем процессе перед завершением работы. Используйте, если вам необходимо что выполнить перед завершением дочернего процесса. 55 | 56 | 57 | ###### onCleanup 58 | 59 | Вызывается в дочернем и в родительском процесе при очистке ресурсов. Используйте, если вам необходимо что либо подчистить/закрыть. 60 | -------------------------------------------------------------------------------- /docs/ru/5.Signals.md: -------------------------------------------------------------------------------- 1 | Обработка POSIX сигналов в дочернем и родительском процессе 2 | =========================================================== 3 | 4 | * [↰ назад к оглавлению документации](0.Index.md) 5 | * [↰ назад к общей информации об AzaThread](../../../../#azathread) 6 | 7 | 8 | --- 9 | 10 | 11 | 1. [Общая информация](#1----) 12 | 2. [Отправка сигналов](#2----) 13 | 14 | 15 | 16 | #### 1 - Общая информация 17 | 18 | С помощью AzaThread вы можете обрабатывать POSIX сигналы в родительском и в дочернем процессе. В дочернем процессе всегда слушаются все доступные сигналы. В родительском это выполняется только при включенной опции `$listenMasterSignals` (по умолчанию включена). 19 | 20 | Для обработки сигнала достаточно объявить метод, который будет вызываться при его получении. Для дочернего процесса название метода равно названию сигнала (регистронезависимо). Первым аргументом этот метод получит число - код сигнала. 21 | 22 | ```php 23 | class ExampleThread extends Thread 24 | { 25 | function process() 26 | { 27 | // ... 28 | } 29 | 30 | /** 31 | * Обработчик SIGUSR1 для дочернего процесса 32 | * 33 | * @param int $signo 34 | */ 35 | protected function sigUsr1($signo) 36 | { 37 | // ... 38 | } 39 | } 40 | ``` 41 | 42 | 43 | Для родительского процесса все практически также, только метод должен быть статическим и с префиксом **"m"**. 44 | 45 | ```php 46 | class ExampleThread extends Thread 47 | { 48 | function process() 49 | { 50 | // ... 51 | } 52 | 53 | /** 54 | * Обработчик SIGUSR2 для родительского процесса 55 | * 56 | * @param int $signo 57 | */ 58 | protected static function mSigUsr2($signo) 59 | { 60 | // ... 61 | } 62 | } 63 | ``` 64 | 65 | 66 | 67 | #### 2 - Отправка сигналов 68 | 69 | Отправлять сигналы также просто, как и получать. Для этого доступны два публичных метода: `sendSignalToParent`, `sendSignalToChild`. Которые отправляют сигнал родительскому и дочернему процессу соответственно. 70 | 71 | ```php 72 | $thread->sendSignalToChild(SIGUSR1); 73 | ``` 74 | 75 | ```php 76 | $this->sendSignalToParent(SIGUSR2); 77 | ``` 78 | -------------------------------------------------------------------------------- /docs/ru/6.Other.md: -------------------------------------------------------------------------------- 1 | Дополнительно 2 | ============= 3 | 4 | * [↰ назад к оглавлению документации](0.Index.md) 5 | * [↰ назад к общей информации об AzaThread](../../../../#azathread) 6 | 7 | 8 | --- 9 | 10 | 11 | 1. [Установка имени процесса](#1-----) 12 | 2. [Дополнительно](#2---) 13 | 14 | 15 | 16 | #### 1 - Установка имени процесса 17 | 18 | AzaThread позволяет установить имя процесса для дочерних процессов (если для этого есть возможность). Для этого нужно передать строку с именем в качестве аргумента для потока или для пула. 19 | 20 | ```php 21 | $processName = 'worker'; 22 | new ExampleThread($processName); 23 | ``` 24 | 25 | ```php 26 | $processName = 'worker'; 27 | new ThreadPool($threadClass, $numberOfThreads, $processName); 28 | ``` 29 | 30 | 31 | 32 | #### 2 - Дополнительно 33 | 34 | Больше информации про устройство AzaThread можно узнать из исходного кода - он хорошо прокомментирован и полностью покрыт тестами. Из тестов также можно узнать больше примеров использования. 35 | 36 | Также вы можете лучше понять функционирование AzaThread, включив режим отладки. Это запускает очень подробное логирование всего, что происходит. Включить очень легко - достаточно установить аргумент `$debug` в `true` для потока или пула потоков. 37 | 38 | ```php 39 | $debug = true; 40 | new ExampleThread(null, null, $debug); 41 | ``` 42 | 43 | ```php 44 | $debug = true; 45 | new ThreadPool($threadClass, $numberOfThreads, null, null, $debug); 46 | ``` 47 | 48 | ```php 49 | $debug = true; 50 | $thread = SimpleThread::create(function() { 51 | // ... 52 | }, null, $debug); 53 | ``` 54 | 55 | 56 | Если вы обнаружили ошибку или у вас есть предложения по улучшению AzaThread, пожалуйста, не стесняйтесь открывать пул реквесты и запросы в баг трекере. 57 | -------------------------------------------------------------------------------- /docs/ru/Examples.md: -------------------------------------------------------------------------------- 1 | Примеры использования 2 | ===================== 3 | 4 | * [↰ назад к оглавлению документации](0.Index.md) 5 | * [↰ назад к общей информации об AzaThread](../../../../#azathread) 6 | 7 | 8 | 9 | --- 10 | 11 | 12 | 13 | 1. [Простой запуск асинхронных вычислений](#example-1------) 14 | 2. [Запуск с аргументом и получением результата](#example-2--------) 15 | 3. [Генерирование событий из потока](#example-3------) 16 | 4. [Использование пула с 8 потоками](#example-4------8-) 17 | 5. [Поток с замыканием](#example-5-----) 18 | 19 | 20 | #### Example #1 - Простой запуск асинхронных вычислений 21 | 22 | ```php 23 | class ExampleThread extends Thread 24 | { 25 | function process() 26 | { 27 | // Код, выполняемый асинхронно 28 | } 29 | } 30 | 31 | $thread = new ExampleThread(); 32 | $thread->wait()->run(); 33 | ``` 34 | 35 | 36 | #### Example #2 - Запуск с аргументом и получением результата 37 | 38 | ```php 39 | class ExampleThread extends Thread 40 | { 41 | function process() 42 | { 43 | return $this->getParam(0); 44 | } 45 | } 46 | 47 | $thread = new ExampleThread(); 48 | $result = $thread->wait()->run(123)->wait()->getResult(); 49 | ``` 50 | 51 | 52 | #### Example #3 - Генерирование событий из потока 53 | 54 | ```php 55 | class ExampleThread extends Thread 56 | { 57 | const EV_PROCESS = 'process'; 58 | 59 | function process() 60 | { 61 | $events = $this->getParam(0); 62 | for ($i = 0; $i < $events; $i++) { 63 | $event_data = $i; 64 | $this->trigger(self::EV_PROCESS, $event_data); 65 | } 66 | } 67 | } 68 | 69 | $thread = new ExampleThread(); 70 | 71 | // Дополнительный аргумент - будет передаваться вместе с аргументами события. 72 | $additionalArgument = 123; 73 | 74 | $thread->bind(ExampleThread::EV_PROCESS, function($event_name, $event_data, $additional_arg) { 75 | // Event handling 76 | }, $additionalArgument); 77 | 78 | $events = 10; // сколько событий сгенерировать 79 | 80 | // Можно переопределить параметр "preforkWait" в TRUE, 81 | // чтобы не вызывать ожидание вручную в первый раз. 82 | // В этом случае ожидание инициализации будет происходить автоматически, 83 | // но эффективнее этого не делать. 84 | $thread->wait(); 85 | 86 | $thread->run($events)->wait(); 87 | ``` 88 | 89 | 90 | #### Example #4 - Использование пула с 8 потоками 91 | 92 | ```php 93 | $threads = 8 // Число потоков 94 | $pool = new ThreadPool('ExampleThread', $threads); 95 | 96 | $num = 25; // Число задач 97 | $left = $num; // Сколько задач осталось выполнить 98 | 99 | do { 100 | // Если есть задачи для выполнения 101 | // и в пуле есть свободные потоки 102 | while ($left > 0 && $pool->hasWaiting()) { 103 | // После старта задачи вы получаете ID 104 | // потока, который начал ее выполнять 105 | $threadId = $pool->run(); 106 | $left--; 107 | } 108 | if ($results = $pool->wait($failed)) { 109 | foreach ($results as $threadId => $result) { 110 | // Задача успешно выполнена 111 | // Результат может быть идентифицирован 112 | // с помощью ID потока ($threadId) 113 | $num--; 114 | } 115 | } 116 | if ($failed) { 117 | // Обработка ошибок. 118 | // Задачу не удалось выполнить по причине смерти 119 | // дочернего процесса или по истечении таймаута 120 | // на выполнение задачи. 121 | foreach ($failed as $threadId => $err) { 122 | list($errorCode, $errorMessage) = $err; 123 | $left++; 124 | } 125 | } 126 | } while ($num > 0); 127 | 128 | // Завершение дочерних процессов. Очистка ресурсов, использованных в пуле. 129 | $pool->cleanup(); 130 | ``` 131 | 132 | 133 | #### Example #5 - Поток с замыканием 134 | 135 | Вы можете использовать упрощенное создание потоков с помощью замыканий. Такие потоки, по умолчанию, не создают сразу дочерний процесс и не мультизадачны (дочерний процесс умирает после каждой задачи). Это может быть изменено с помощью второго аргумента в `SimpleThread::create`. 136 | 137 | ```php 138 | $result = SimpleThread::create(function($arg) { 139 | return $arg; 140 | })->run(123)->wait()->getResult(); 141 | ``` 142 | 143 | 144 | 145 | --- 146 | 147 | 148 | 149 | Остальные примеры можно найти в файле [examples/example.php](../examples/example.php) и в юнит тестах [Tests/ThreadTest.php](../Tests/ThreadTest.php). 150 | 151 | Также вы можете выполнить тесты производительности, выбрать подходящее число потоков и лучшие настройки для вашей системы с использованием [examples/speed_test.php](../examples/speed_test.php). 152 | -------------------------------------------------------------------------------- /examples/example.php: -------------------------------------------------------------------------------- 1 | 20 | * @license MIT 21 | */ 22 | 23 | 24 | 25 | /** 26 | * Test thread 27 | */ 28 | class TestThreadReturnFirstArgument extends Thread 29 | { 30 | /** 31 | * {@inheritdoc} 32 | */ 33 | function process() 34 | { 35 | return $this->getParam(0); 36 | } 37 | } 38 | 39 | /** 40 | * Test thread 41 | */ 42 | class TestThreadEvents extends Thread 43 | { 44 | const EV_PROCESS = 'process'; 45 | 46 | /** 47 | * {@inheritdoc} 48 | */ 49 | function process() 50 | { 51 | $events = $this->getParam(0); 52 | for ($i = 0; $i < $events; $i++) { 53 | $this->trigger(self::EV_PROCESS, $i); 54 | } 55 | } 56 | } 57 | 58 | 59 | // Checks 60 | if (!Thread::$useForks) { 61 | echo PHP_EOL, "You do not have the minimum system requirements to work in async mode!!!"; 62 | if (!Base::$hasForkSupport) { 63 | echo PHP_EOL, "You don't have pcntl or posix extensions installed or either not CLI SAPI environment!"; 64 | } 65 | if (!EventBase::$hasLibevent) { 66 | echo PHP_EOL, "You don't have libevent extension installed!"; 67 | } 68 | echo PHP_EOL; 69 | } 70 | 71 | 72 | // ---------------------------------------------- 73 | echo PHP_EOL, 74 | 'Simple example with one thread', 75 | PHP_EOL; 76 | 77 | $num = 10; // Number of tasks 78 | $thread = new TestThreadReturnFirstArgument(); 79 | 80 | // You can override preforkWait property 81 | // to TRUE to not wait thread at first time manually 82 | $thread->wait(); 83 | 84 | for ($i = 0; $i < $num; $i++) { 85 | $value = $i; 86 | // Run task and wait for the result 87 | if ($thread->run($value)->wait()->getSuccess()) { 88 | // Success 89 | $result = $thread->getResult(); 90 | echo 'result: ' . $result . PHP_EOL; 91 | } else { 92 | // Error handling here 93 | // processing is not successful if thread dies 94 | // when worked or working timeout exceeded 95 | echo 'error' . PHP_EOL; 96 | } 97 | } 98 | 99 | // After work it's strongly recommended to clean 100 | // resources obviously to avoid leaks 101 | $thread->cleanup(); 102 | 103 | 104 | 105 | // ---------------------------------------------- 106 | echo PHP_EOL, 107 | 'Simple example with thread events', 108 | PHP_EOL; 109 | 110 | $events = 10; // Number of events 111 | $num = 3; // Number of tasks 112 | 113 | $thread = new TestThreadEvents(); 114 | 115 | // You can override preforkWait property 116 | // to TRUE to not wait thread at first time manually 117 | $thread->wait(); 118 | 119 | $cb = function($event_name, $event_data) { 120 | echo "event: $event_name : ", $event_data, PHP_EOL; 121 | }; 122 | $thread->bind(TestThreadEvents::EV_PROCESS, $cb); 123 | 124 | for ($i = 0; $i < $num; $i++) { 125 | $thread->run($events)->wait(); 126 | echo 'task ended', PHP_EOL; 127 | } 128 | // After work it's strongly recommended to clean 129 | // resources obviously to avoid leaks 130 | $thread->cleanup(); 131 | 132 | 133 | 134 | // ---------------------------------------------- 135 | $threads = 4; 136 | 137 | echo PHP_EOL, 138 | "Simple example with pool of threads ($threads)", 139 | PHP_EOL; 140 | 141 | $pool = new ThreadPool('TestThreadReturnFirstArgument', $threads); 142 | 143 | $num = 25; // Number of tasks 144 | $left = $num; // Number of remaining tasks 145 | do { 146 | while ($left > 0 && $pool->hasWaiting()) { 147 | if (!$threadId = $pool->run($left)) { 148 | throw new Exception('Pool slots error'); 149 | } 150 | $left--; 151 | } 152 | if ($results = $pool->wait($failed)) { 153 | foreach ($results as $threadId => $result) { 154 | $num--; 155 | echo "result: $result (thread $threadId)", PHP_EOL; 156 | } 157 | } 158 | if ($failed) { 159 | // Error handling here 160 | // processing is not successful if thread dies 161 | // when worked or working timeout exceeded 162 | foreach ($failed as $threadId => $err) { 163 | list($errorCode, $errorMessage) = $err; 164 | echo "error (thread $threadId): #$errorCode - $errorMessage", PHP_EOL; 165 | $left++; 166 | } 167 | } 168 | } while ($num > 0); 169 | // After work it's strongly recommended to clean 170 | // resources obviously to avoid leaks 171 | $pool->cleanup(); 172 | 173 | 174 | 175 | // ---------------------------------------------- 176 | $threads = 8; 177 | $jobs = range(1, 30); 178 | $jobs_num = count($jobs); 179 | 180 | echo PHP_EOL, 181 | "Example with pool of threads ($threads) and pool of jobs ($jobs_num)", 182 | PHP_EOL; 183 | 184 | $pool = new ThreadPool('TestThreadReturnFirstArgument', $threads); 185 | 186 | $num = $jobs_num; // Number of tasks 187 | $left = $jobs_num; // Number of remaining tasks 188 | $started = array(); 189 | do { 190 | while ($left > 0 && $pool->hasWaiting()) { 191 | $task = array_shift($jobs); 192 | if (!$threadId = $pool->run($task)) { 193 | throw new Exception('Pool slots error'); 194 | } 195 | $started[$threadId] = $task; 196 | $left--; 197 | } 198 | if ($results = $pool->wait($failed)) { 199 | foreach ($results as $threadId => $result) { 200 | unset($started[$threadId]); 201 | $num--; 202 | echo "result: $result (thread $threadId)", PHP_EOL; 203 | } 204 | } 205 | if ($failed) { 206 | // Error handling here 207 | // processing is not successful if thread dies 208 | // when worked or working timeout exceeded 209 | foreach ($failed as $threadId => $err) { 210 | list($errorCode, $errorMessage) = $err; 211 | $jobs[] = $started[$threadId]; 212 | echo "error: {$started[$threadId]} ", 213 | "(thread $threadId): #$errorCode - $errorMessage", PHP_EOL; 214 | unset($started[$threadId]); 215 | $left++; 216 | } 217 | } 218 | } while ($num > 0); 219 | // After work it's strongly recommended to clean 220 | // resources obviously to avoid leaks 221 | $pool->cleanup(); 222 | 223 | 224 | 225 | // ---------------------------------------------- 226 | echo PHP_EOL, 227 | 'Simple example with one "closure" thread', 228 | PHP_EOL; 229 | 230 | $num = 10; // Number of tasks 231 | $thread = SimpleThread::create(function($arg) { 232 | return $arg; 233 | }); 234 | 235 | // "closure" threads are not preforked by default 236 | // and not multitask too. You can change it via 237 | // the second argument of `SimpleThread::create`. 238 | 239 | for ($i = 0; $i < $num; $i++) { 240 | $value = $i; 241 | // Run task and wait for the result 242 | if ($thread->run($value)->wait()->getSuccess()) { 243 | // Success 244 | $result = $thread->getResult(); 245 | echo 'result: ' . $result . PHP_EOL; 246 | } else { 247 | // Error handling here 248 | // processing is not successful if thread dies 249 | // when worked or working timeout exceeded 250 | echo 'error' . PHP_EOL; 251 | } 252 | } 253 | // After work it's strongly recommended to clean 254 | // resources obviously to avoid leaks 255 | $thread->cleanup(); 256 | -------------------------------------------------------------------------------- /examples/speed_test.php: -------------------------------------------------------------------------------- 1 | 16 | * @license MIT 17 | */ 18 | 19 | /** 20 | * Threads speed test results in jobs per second 21 | * 22 | * ================================================= 23 | * 24 | * Intel Core i3 540 3.07 Ghz (Ubuntu 11.04) results 25 | * 26 | * IPC (empty jobs): 27 | * 28 | * 20219 - 1 thread (Sync) 29 | * 19900 - 1 thread (Sync, Data transfer) 30 | * 31 | * 4460 - 1 thread (Async) 32 | * 3082 - 1 thread (Async, Data transfer) 33 | * 34 | * 5224 - 2 threads (Async) 35 | * 3894 - 2 threads (Async, Data transfer) 36 | * 37 | * 5718 - 4 threads (Async) 38 | * 4295 - 4 threads (Async, Data transfer) 39 | * 40 | * 5806 - 8 threads (Async) 41 | * 4371 - 8 threads (Async, Data transfer) 42 | * 43 | * 5842 - 10 threads (Async) 44 | * 4303 - 10 threads (Async, Data transfer) 45 | * 46 | * 5890 - 12 threads (Async) 47 | * 4333 - 12 threads (Async, Data transfer) 48 | * 49 | * 6018 - 16 threads (Async) 50 | * 4234 - 16 threads (Async, Data transfer) 51 | * 52 | * Working jobs: 53 | * 54 | * 553 - 1 thread (Sync) 55 | * 330 - 1 thread (Async) 56 | * 580 - 2 threads (Async) 57 | * 1015 - 4 threads (Async) 58 | * 1040 - 8 threads (Async) 59 | * 1027 - 10 threads (Async) 60 | * 970 - 12 threads (Async) 61 | * 958 - 16 threads (Async) 62 | * 63 | * ================================================= 64 | * 65 | * Intel Core i7 2600K 3.40 Ghz (Ubuntu 11.04 on VMware virtual machine) results 66 | * 67 | * IPC (empty jobs): 68 | * 69 | * 26394 - 1 thread (Sync) 70 | * 25032 - 1 thread (Sync, Data transfer) 71 | * 72 | * 4928 - 1 thread (Async) 73 | * 4164 - 1 thread (Async, Data transfer) 74 | * 75 | * 7210 - 2 threads (Async) 76 | * 5910 - 2 threads (Async, Data transfer) 77 | * 78 | * 7129 - 4 threads (Async) 79 | * 6261 - 4 threads (Async, Data transfer) 80 | * 81 | * 7633 - 8 threads (Async) 82 | * 6630 - 8 threads (Async, Data transfer) 83 | * 84 | * 7810 - 10 threads (Async) 85 | * 6715 - 10 threads (Async, Data transfer) 86 | * 87 | * 7641 - 12 threads (Async) 88 | * 6540 - 12 threads (Async, Data transfer) 89 | * 90 | * 7587 - 16 threads (Async) 91 | * 6514 - 16 threads (Async, Data transfer) 92 | * 93 | * Working jobs: 94 | * 95 | * 763 - 1 thread (Sync) 96 | * 669 - 1 thread (Async) 97 | * 1254 - 2 threads (Async) 98 | * 2188 - 4 threads (Async) 99 | * 2618 - 8 threads (Async) 100 | * 2719 - 10 threads (Async) 101 | * 2739 - 12 threads (Async) 102 | * 2904 - 16 threads (Async) 103 | * 2830 - 18 threads (Async) 104 | * 2730 - 20 threads (Async) 105 | * 106 | * ================================================= 107 | */ 108 | 109 | 110 | 111 | 112 | ############# 113 | # Settings 114 | ############# 115 | 116 | $data = true; // Transmit data 117 | $work = true; // Do some work 118 | $tests = 6; // Number of iterations in tests 119 | $jobsT = $work ? 2500 : 10000; // Number of jobs to do in one thread 120 | $jobsP = $work ? 10000 : 20000; // Number of jobs to do in pools 121 | $poolMin = 2; // Minimum threads number in pool to test 122 | 123 | // Disable to use sync mode 124 | //Thread::$useForks = false; 125 | 126 | // Manually specify the type of data transfer between threads 127 | //Thread::$ipcDataMode = Thread::IPC_IGBINARY; 128 | 129 | // Use stream sockets 130 | //Socket::$useSockets = false; 131 | 132 | 133 | 134 | ############# 135 | # Test 136 | ############# 137 | 138 | 139 | /** 140 | * Test thread 141 | */ 142 | class TestThreadNothing extends Thread 143 | { 144 | /** 145 | * Main processing. 146 | * 147 | * @return mixed 148 | */ 149 | function process() {} 150 | } 151 | 152 | /** 153 | * Test thread 154 | */ 155 | class TestThreadReturn extends Thread 156 | { 157 | /** 158 | * Main processing. 159 | * 160 | * @return mixed 161 | */ 162 | function process() 163 | { 164 | return array(123456789, 'abcdefghigklmnopqrstuvwxyz', 123.7456328); 165 | } 166 | } 167 | 168 | /** 169 | * Test thread 170 | */ 171 | class TestThreadWork extends Thread 172 | { 173 | /** 174 | * Main processing. 175 | * 176 | * @return mixed 177 | */ 178 | function process() 179 | { 180 | $r = null; 181 | $i = 1000; 182 | while ($i--) { 183 | $r = mt_rand(0, PHP_INT_MAX) * mt_rand(0, PHP_INT_MAX); 184 | } 185 | return $r; 186 | } 187 | } 188 | 189 | 190 | 191 | /** 192 | * Prints message 193 | * 194 | * @parma string $msg Message 195 | * @parma int $newline Newlines count 196 | */ 197 | $print = function($msg = '', $newline = 1) { 198 | echo $msg . str_repeat(PHP_EOL, (int)$newline); 199 | @ob_flush(); @flush(); 200 | }; 201 | $line = str_repeat('-', 80); 202 | 203 | 204 | if (!Thread::$useForks) { 205 | $print( 206 | 'ERROR: You need Forks, LibEvent, PCNTL and POSIX' 207 | .' support with CLI sapi to fully test Threads' 208 | ); 209 | return; 210 | } 211 | 212 | 213 | /** @var $threadClass Thread */ 214 | $threadClass = $work ? 'TestThreadWork' : ($data ? 'TestThreadReturn' : 'TestThreadNothing'); 215 | $threadClass = __NAMESPACE__ . '\\' . $threadClass; 216 | 217 | $arg1 = (object)array('foobarbaz' => 1234567890, 12.9876543); 218 | $arg2 = 123/16; 219 | 220 | 221 | 222 | // Test one thread 223 | $print($line); 224 | $print("One thread test; Jobs: $jobsT; Iterations: $tests"); 225 | $print($line, 2); 226 | 227 | /** @var $thread Thread */ 228 | $thread = new $threadClass; 229 | $thread->wait(); 230 | $res = array(); 231 | 232 | for ($j = 0; $j < $tests; ++$j) { 233 | $start = microtime(true); 234 | for ($i = 0; $i < $jobsT; $i++) { 235 | $data ? $thread->run($arg1, $arg2) : $thread->run(); 236 | $thread->wait()->getResult(); 237 | } 238 | $end = bcsub(microtime(true), $start, 99); 239 | $oneJob = bcdiv($end, $jobsT, 99); 240 | $res[] = bcdiv(1, $oneJob, 99); 241 | $jps = bcdiv(1, $oneJob); 242 | $print("Iteration: ".($j+1)."; Jobs per second: $jps"); 243 | } 244 | $sum = 0; 245 | foreach ($res as $r) { 246 | $sum = bcadd($sum, $r, 99); 247 | } 248 | $averageOneThreadJps = bcdiv($sum, $tests); 249 | $print("Average jobs per second: $averageOneThreadJps", 3); 250 | 251 | $thread->cleanup(); 252 | 253 | 254 | 255 | // Test pools 256 | $bestJps = 0; 257 | $bestThreadsNum = 0; 258 | $regression = 0; 259 | $lastJps = 0; 260 | $print($line); 261 | $print("Pool test; Jobs: $jobsP; Iterations: $tests"); 262 | $print($line, 2); 263 | 264 | $threads = $poolMin; 265 | $pool = new ThreadPool($threadClass, $threads); 266 | do { 267 | $print("Threads: $threads"); 268 | $print($line); 269 | 270 | $res = array(); 271 | for ($j = 0; $j < $tests; ++$j) { 272 | $start = microtime(true); 273 | 274 | $num = $jobsP; 275 | $i = 0; 276 | $maxI = ceil($jobsP * 1.5); 277 | do { 278 | while ($pool->hasWaiting()) { 279 | $data ? $pool->run($arg1, $arg2) : $pool->run(); 280 | } 281 | if ($results = $pool->wait()) { 282 | $num -= count($results); 283 | } 284 | $i++; 285 | } while ($num > 0 && $i < $maxI); 286 | 287 | $end = bcsub(microtime(true), $start, 99); 288 | $oneJob = bcdiv($end, $jobsP, 99); 289 | $res[] = bcdiv(1, $oneJob, 99); 290 | $jps = bcdiv(1, $oneJob); 291 | $print("Iteration: ".($j+1)."; Jobs per second: $jps"); 292 | } 293 | $sum = 0; 294 | foreach ($res as $r) { 295 | $sum = bcadd($sum, $r, 99); 296 | } 297 | $avJps = bcdiv($sum, $tests); 298 | $print("Average jobs per second: $avJps", 2); 299 | 300 | if ($bestJps < $avJps) { 301 | $bestJps = $avJps; 302 | $bestThreadsNum = $threads; 303 | } 304 | if ($avJps < $bestJps || $avJps < $lastJps) { 305 | $regression++; 306 | } 307 | if ($regression >= 3) { 308 | break; 309 | } 310 | $lastJps = $avJps; 311 | 312 | // Increase number of threads 313 | $threads++; 314 | $pool->setMaxThreads($threads); 315 | } while (true); 316 | 317 | $pool->cleanup(); 318 | 319 | 320 | $print('', 3); 321 | $print("Best number of threads for your system: {$bestThreadsNum} ({$bestJps} jobs per second)"); 322 | 323 | $boost = bcdiv($bestJps, bcdiv($averageOneThreadJps, 100, 99), 2); 324 | $print("Performance boost in relation to a single thread: {$boost}%"); 325 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | ./Tests/ 27 | 28 | 29 | 30 | 31 | 32 | benchmark 33 | performance 34 | 35 | 36 | 37 | 38 | 39 | ./ 40 | 41 | ./examples 42 | ./Tests 43 | ./vendor 44 | 45 | 46 | 47 | 48 | --------------------------------------------------------------------------------