1
0
mirror of https://github.com/danog/parallel.git synced 2024-11-30 04:39:01 +01:00
Go to file
2015-09-27 23:45:56 -05:00
benchmarks Add benchmark for socket pairs 2015-08-03 00:28:38 -05:00
bin Add worker environment 2015-09-09 23:29:41 -05:00
examples Update examples with BlockingTask 2015-09-27 22:46:57 -05:00
src Update examples with BlockingTask 2015-09-27 22:46:57 -05:00
tests Fix bug reading long data from channel 2015-09-27 22:34:50 -05:00
.gitattributes Add benchmarks for profiling implementations and backends 2015-07-16 03:02:58 -05:00
.gitignore Add vagrant to gitignore 2015-08-02 19:45:44 -05:00
.travis.yml Add Slack <- Travis integration for fun 2015-09-14 23:14:21 -05:00
.valgrindrc Add helpful defaults for Valgrind 2015-08-27 12:33:19 -05:00
CHANGELOG.md Add changelog that we'll need later 2015-08-27 12:40:35 -05:00
composer.json Update examples with BlockingTask 2015-09-27 22:46:57 -05:00
CONTRIBUTING.md Initial commit 2015-07-10 15:15:42 -05:00
LICENSE Update license 2015-09-14 21:28:25 -05:00
phpdoc.dist.xml Initial commit 2015-07-10 15:15:42 -05:00
phpunit.xml.dist Merge back into one test suite 2015-08-05 11:29:22 -05:00
README.md Update documentation 2015-09-27 23:45:56 -05:00
Vagrantfile Update Vagrant box to use pthreads latest 2015-08-22 01:02:41 -05:00

Concurrency for Icicle

True concurrency using native threading and multiprocessing for parallelizing code, without blocking.

This library is a component for Icicle that provides native threading, multiprocessing, process synchronization, shared memory, and task workers. Like other Icicle components, this library uses Promises and Generators for asynchronous operations that may be used to build Coroutines to make writing asynchronous code more like writing synchronous code.

Build Status Coverage Status Semantic Version MIT License @icicleio on Twitter

This library provides a means of parallelizing code without littering your application with complicated lock checking and inter-process communication.

To be as flexible as possible, this library comes with a collection of non-blocking concurrency tools that can be used independently as needed, as well as an "opinionated" worker API that allows you to assign units of work to a pool of worker threads or processes.

Requirements
  • PHP 5.5+
Suggested
  • pthreads extension: Best extension option for concurrency in PHP, but it requires PHP to be compiled with --enable-maintainer-zts to enable thread-safety. or
  • pcntl extension: Enables forking concurrency method.
  • sysvmsg extension: Required for sharing memory between forks or processes.
Installation

The recommended way to install is with the Composer package manager. (See the Composer installation guide for information on installing and using Composer.)

Run the following command to use Icicle in your project:

composer require icicleio/concurrent

You can also manually edit composer.json to add this library as a project requirement.

// composer.json
{
    "require": {
        "icicleio/concurrent": "^0.1"
    }
}

While the pthreads is not necessary for this package, it is the fastest and best choice for concurrency in PHP. To enable threading, you will need to compile pthreads from source, as this package depends on unstable and unreleased fixes in pthreads.

git clone https://github.com/krakjoe/pthreads && cd pthreads
git checkout master
phpize
./configure
make
sudo make install
Benchmarks

A few benchmarks are provided for analysis and study. Can be used to back up implementation decisions, or to measure performance on different platforms or hardware.

vendor/bin/athletic -p benchmarks -b vendor/autoload.php

Documentation

Concurrent can use either process forking, processes created using proc_open(), or true threading to parallelize execution. Threading provides better performance and is compatible with Unix and Windows but requires ZTS (Zend thread-safe) PHP, while forking has no external dependencies but is only compatible with Unix systems.

Threads

Threading is a cross-platform concurrency method that is fast and memory efficient. Thread contexts take advantage of an operating system's multi-threading capabilities to run code in parallel. A spawned thread will run completely parallel to the parent thread, each with its own environment. Each thread is assigned a closure to execute when it is created, and the returned value is passed back to the parent thread. Concurrent goes for a "shared-nothing" architecture, so any variables inside the closure are local to that thread and can store any non-safe data.

You can spawn a new thread with the Thread::spawn() method:

use Icicle\Concurrent\Threading\Thread;
use Icicle\Coroutine;
use Icicle\Loop;

Coroutine\create(function () {
    $thread = Thread::spawn(function () {
        print "Hello, World!\n";
    });

    yield $thread->join();
});

Loop\run();

You can wait for a thread to finish by calling join(). Joining does not block the parent thread and will asynchronously wait for the child thread to finish before resolving.

Forks

For Unix-like systems, you can create parallel execution using fork contexts. Though not as efficient as multi-threading, in some cases forking can take better advantage of some multi-core processors than threads. Fork contexts use the pcntl_fork() function to create a copy of the current process and run alternate code inside the new process.

Spawning and controlling forks are quite similar to creating threads. To spawn a new fork, use the Fork::spawn() method:

use Icicle\Concurrent\Forking\Fork;
use Icicle\Coroutine;
use Icicle\Loop;

Coroutine\create(function () {
    $fork = Fork::spawn(function () {
        print "Hello, World!\n";
    });

    yield $fork->join();
});

Loop\run();

Calling join() on a fork will asynchronously wait for the forked process to terminate, similar to the pcntl_wait() function.

Synchronization with Channels

Threads and forks wouldn't be very useful if they couldn't be given any data to work on. The recommended way to share data between contexts is with a Channel. A channel is a low-level abstraction over local, non-blocking sockets, which can be used to pass messages and objects between two contexts. Channels are non-blocking and do not require locking. For example:

use Icicle\Concurrent\Sync\Channel;
use Icicle\Concurrent\Threading\Thread;
use Icicle\Coroutine;
use Icicle\Loop;

Coroutine\create(function () {
    $thread = Thread::spawn(function () {
        $time = (yield $this->receive()); // Receive from the parent.
        sleep($time);
        yield $this->send("Hello!"); // Send to the parent.
    });

    yield $thread->send(3); // Send 3 to the context.

    $message = (yield $thread->receive()); // Receive from the context.
    yield $thread->join();
    
    print $message . "\n";
});

Loop\run();

Thread and fork execution contexts include a channel to communicate with the parent and context. The channel methods send() and receive() may be invoked using $this within the function executed in the context and on the context object in the parent. See the example above.

Synchronization with Parcels

Parcels are shared containers that allow you to store context-safe data inside a shared location so that it can be accessed by multiple contexts. To prevent race conditions, you still need to access a parcel's data exclusively, but Concurrent allows you to acquire a lock on a parcel asynchronously without blocking the context execution, unlike traditional mutexes.

Development and Contributing

Interested in contributing to Icicle? Please see our contributing guidelines in the Icicle repository.

Want to hack on the source? A Vagrant box is provided with the repository to give a common development environment for running concurrent threads and processes, and comes with a bunch of handy tools and scripts for testing and experimentation.

Starting up and logging into the virtual machine is as simple as

vagrant up && vagrant ssh

Once inside the VM, you can install PHP extensions with Pickle, switch versions with newphp VERSION, and test for memory leaks with Valgrind.