An open API service indexing awesome lists of open source software.

https://github.com/tpunt/pht

A new threading extension for PHP
https://github.com/tpunt/pht

concurrency multithreading parallelism php php-extension pthreads threading

Last synced: 5 months ago
JSON representation

A new threading extension for PHP

Awesome Lists containing this project

README

          

# The Pht Threading Extension

This extension exposes a new approach to threading in PHP.

Quick feature list:
- Classes, functions, and files may be threaded
- The inter-thread communication (ITC) data structures include: hash table, queue, vector
- Threads are reusable for any number of tasks

Requirements:
- A ZTS version of PHP 7.2. The master branch of php-src is not currently compatible

Any Unix-based OS is supported (including OS X), along with Windows. This extension was explicitly tested on OS X (Yosemite and Sierra), Ubuntu 14.04 (32bit), and Windows Server 2012 (the pthreads-win32 library is needed).

Documentation: [php.net/pht](http://php.net/pht)

Contents:
- [Installation](https://github.com/tpunt/pht#installation)
- [Pthreads VS pht](https://github.com/tpunt/pht#pthreads-vs-pht)
- [The Basics](https://github.com/tpunt/pht#the-basics)
- [API](https://github.com/tpunt/pht#api)
- [Quick Examples](https://github.com/tpunt/pht#quick-examples)
- [Threading Types](https://github.com/tpunt/pht#threading-types)
- [Class Threading](https://github.com/tpunt/pht#class-threading)
- [Function Threading](https://github.com/tpunt/pht#function-threading)
- [File Threading](https://github.com/tpunt/pht#file-threading)
- [Inter-Thread Communication Data Structures](https://github.com/tpunt/pht#inter-thread-communication-data-structures)
- [Queue](https://github.com/tpunt/pht#queue)
- [Vector](https://github.com/tpunt/pht#vector)
- [Hash Table](https://github.com/tpunt/pht#hash-table)
- [Atomic Values](https://github.com/tpunt/pht#atomic-values)
- [Atomic Integer](https://github.com/tpunt/pht#atomic-integer)

This extension was built using a few ideas from the [pthreads](https://github.com/krakjoe/pthreads) extension. I'd therefore like to give credit to, as well as thank, [Joe Watkins](https://github.com/krakjoe) for his great work on the pthreads project!

## Installation

If you're using a Unix-based OS, then you can build the extension from source by:
```php
git clone https://github.com/tpunt/pht
cd pht
git checkout tags/v0.0.1
phpize
./configure
make
make install
```

If you're using Windows, see the [release page](https://github.com/tpunt/pht/releases) for the appropriate .dll extension file. The pthreadVC2.dll file (distributed alongside the extension's .dll file) will need to be made available in your `PATH` environment variable.

Once the .so or .dll file has been acquire, the php.ini file will then need to be updated (to load the extension) with:
```
extension="path/to/pht_file"
```

## Pthreads vs pht

Both extensions have their own advantages and disadvantages.

Pthreads advantages over pht:
- Uses synchronised blocks over mutex locks (which can be cleaner to use and harder to mess up)
- Easier communication between threads (no knowledge of data structures is required, which PHP developers tend to lack in)
- Maturer

Pht advantages over pthreads:
- No serialisation of properties on threaded objects, meaning:
- No performance hit when reading from or writing to properties
- No unfamiliar semantics, such as implicitly casting arrays to `Volatile` objects or property immutability for some types
- No limitations on what types of data can be stored as properties on threaded objects
- The ability to thread functions and files (as well as classes)
- Threaded classes implement a `Runnable` interface, rather than having to inherit (so more flexibility on inheritance)

## The Basics

This approach to threading abstracts away the thread itself behind a dedicated object (`Thread`), where tasks are added to that thread's internal task queue.

Example:
```php
addClassTask(Task::class);

// Thread::addFunctionTask(callable $fn, mixed ...$fnArgs) : void;
$thread->addFunctionTask(function ($zero) {var_dump($zero);}, 0);

// Thread::addFileTask(string $filename, mixed ...$globals) : void;
$thread->addFileTask('some_file.php', 1, 2, 3);

$thread->start();
$thread->join();
```

`some_file.php`:
```php
one = $one;
}

public function run()
{
var_dump($this->one);
}
}

$thread = new Thread();

$thread->addClassTask(Task::class, 1);

$thread->start();
$thread->join();
```

All `$ctorArgs` being passed into the `Thread::addClassTask()` method will be serialised.

#### Function Threading

Functions must not refer to `$this` (it will become `null` in the threaded context), and must not import variables from their outer scope (via the `use` statement). They should be completely standalone.

```php
addFunctionTask(static function($one) {var_dump($one);}, 1);
$thread->addFunctionTask(function() {var_dump(2);});
$thread->addFunctionTask('aFunc');
$thread->addFunctionTask('array_map', function ($n) {var_dump($n);}, [4]);
$thread->addFunctionTask(['Test', 'run']);
$thread->addFunctionTask([new Test, 'run2']);

$thread->start();
$thread->join();
```

All `$fnArgs` being passed into the `Thread::addFunctionTask()` method will be serialised.

#### File Threading

To pass data to the file being threaded, pass them as additional arguments to the `Thread::addFileTask()` method. They will then become available in a special `$_THREAD` superglobals array inside of the threaded file.

```php
addFileTask('file.php', 1, 2, 3);

$thread->start();
$thread->join();
```

`file.php`
```php
addFunctionTask(function ($queue, $queueItemCount) {
for ($i = 0; $i < $queueItemCount; ++$i) {
$queue->lock();
$queue->push($i);
$queue->unlock();
}
}, $queue, $queueItemCount);

$thread->start();

while (true) {
$queue->lock();

if ($queue->size() === $queueItemCount) {
$queue->unlock();
break;
}

$queue->unlock();
}

$thread->join();

// since we are no longer using $queue in multiple threads, we don't need to lock it
while ($queue->size()) {
var_dump($queue->pop());
}
```

#### Vector

```php
addFunctionTask(function ($vector, $i) {
$vector->lock();
$vector->push($i);
$vector->unlock();
}, $vector, $i);
}

$thread->start();

while (true) {
$vector->lock();

if ($vector->size() === $vectorItemCount) {
$vector->unlock();
break;
}

$vector->unlock();
}

$thread->join();

// since we are no longer using $vector in multiple threads, we don't need to lock it
for ($i = 0; $i < $vectorItemCount; ++$i) {
var_dump($vector[$i]);
}
```

#### Hash Table

```php
addFunctionTask(function ($hashTable, $i) {
$hashTable->lock();
$hashTable[chr(ord('a') + $i)] = $i;
$hashTable->unlock();
}, $hashTable, $i);
}

$thread->start();

while (true) {
$hashTable->lock();

if ($hashTable->size() === $hashTableItemCount) {
$hashTable->unlock();
break;
}

$hashTable->unlock();
}

$thread->join();

// since we are no longer using $hashTable in multiple threads, we don't need to lock it
for ($i = 0; $i < $hashTableItemCount; ++$i) {
var_dump($hashTable[chr(ord('a') + $i)]);
}
```

### Atomic Values

Atomic values are classes that wrap simple values. These values are safe to update without acquiring mutex locks, but they also pack with them mutex locks should multiple operations need to be performed together. The mutex locks, for this reason, are reentrant.

#### Atomic Integer

```php
addFunctionTask(function ($atomicInteger, $max) {
for ($i = 0; $i < $max; ++$i) {
$atomicInteger->inc();
}
}, $atomicInteger, $max);

$thread->start();

// safe
for ($i = 0; $i < $max; ++$i) {
$atomicInteger->inc();
}

// safe
while ($atomicInteger->get() !== $max * 2);

// requires mutex locking since we need to perform multiple operations together
$atomicInteger->lock();
$atomicInteger->set($atomicInteger->get() * 2);
$atomicInteger->unlock();

$thread->join();

var_dump($atomicInteger->get()); // int(400000)
```