
<h2> Multi-Tasking and Multi-Threading Support</h2>

To perform parallel execution in PHP without forking a new process, we can
choose one of these 3 new facilities:

1. Pagelet Server

This is already implemented. A pagelet server is similar to a CURL call to
localhost. Look for "Pagelet Server" in compiled program's options for how to
set it up. The new pagelet server functions work like this,

  // This starts a pagelet server thread to process the URL just as if
  // it's a new web request with specified headers and post data.
  // The request method would be GET if the post data is empty.
  $task = <b>pagelet_server_task_start</b>($url, $headers, $post_data);
  // Main thread can now do extra work while pagelet server is processing.
  ...
  // Optionally make this non-blocking call any time to check status.
  $status = <b>pagelet_server_task_status</b>($task);
  ...
  // Finally, we make a blocking call to wait for pagelet server's result,
  // which is the entire output of the web page, with response headers and
  // status code.
  $headers = array(); $code = 0;
  $result = <b>pagelet_server_task_result</b>($task, $headers, $code);

2. Xbox Tasks

This is already implemented. An xbox system is designed for cross-box messaging
that's described in "server.xbox_server" documentation. But when it runs
locally, it provides parallel execution in a separate thread.

  // We start an xbox task by sending to localhost a message.
  $task = <b>xbox_task_start</b>($message);
  // Main thread can now do extra work while xbox task is processing.
  ...
  // Optionally make this non-blocking call any time to check status.
  $status = <b>xbox_task_status</b>($task);
  ...
  // Finally, we make a blocking call to check message processing returns.
  $ret = null;
  $code = <b>xbox_task_result</b>($task, $timeout_ms, $ret);

On message processing side, one has to implement a PHP function like this,

  function <b>xbox_process_message</b>($msg) {
    ...
    return $ret;
  }

Please note that an xbox thread starts its execution with its own global
states without sharing anything with main thread, other than $msg and $ret that
are passed between these threads at enter and exit points. To share states,
please read on for call_user_func_async() series.

3. call_user_func_async() and call_user_func_array_async()

<b>Note: This functionality is deprecated. Please use one of the alternative
forms of concurrency delineated above.</b>

"Fork" program into 2 or more threads at any time point of the execution from
the same set of global states, then "join" when thread finishes its execution.

  // This is non-blocking, and global states will be duplicated here.
  $handle = <b>call_user_func_async</b>($func, $param1, $param2, ...);
  // Main thread can now do extra work while the other thread is running.
  do_main_thread_jobs();
  $ret = <b>end_user_func_async</b>($handle, $default_strategy,
                                    $additional_strategies);

Please note that global states will be duplicated when call_user_func_async()
is called, so that each thread (the main thread and the new thread) will have
its own copy of the states. From then on, they never share anything (other than
APC). When end_user_func_async() is called, the global state of the new
thread will be "merged" back into main thread's depending on how strategies are
specified.

$default_strategy can be,

(1) GLOBAL_STATE_IGNORE (default)

In this case, new thread's modifications of global states will be thrown away.

(2) GLOBAL_STATE_OVERWRITE

In this case, new thread's modifications of global states will entirely
replace main thread's. Do so ONLY when you are sure main thread's global states
can be lost during this overwrite. Otherwise, please use GLOBAL_STATE_IGNORE
and use function output parameters to pass back global states that need to
be copied back to main thread.

(3) GLOBAL_STATE_SKIP

In this case, new thread's modifications of global states will be merged into
main thread's, and when there is a conflict, new thread's state will not be
merged at all. "Conflict" is currently only defined as an array having the same
first level key with different values. Then the new value under the key will
be skipped. For example, if main thread has array('a' => 1, 'b' => 2) and
new thread has array('a' => 3, 'b' => 4), after merge it would become
array('a' => 1, 'b' => 4). Note that this is NOT recursive and it only applies
to first level keys.

$additional_strategies can be used to specify finer granularity rules:

  array({global symbol type} => array({name} => {strategy}, ...), ...);

where {global symbol type} can be any one of these,

- GLOBAL_SYMBOL_GLOBAL_VARIABLE
- GLOBAL_SYMBOL_STATIC_VARIABLE
- GLOBAL_SYMBOL_CLASS_STATIC
- GLOBAL_SYMBOL_DYNAMIC_CONSTANT
- GLOBAL_SYMBOL_FILE_INCLUDE
- GLOBAL_SYMBOL_REDECLARED_FUNCTION
- GLOBAL_SYMBOL_REDECLARED_CLASS

For example,

  end_user_func_async($handle, GLOBAL_STATE_IGNORE,
        array(GLOBAL_SYMBOL_GLOBAL_VARIABLE =>
                array('CACHE' => GLOBAL_STATE_SKIP)));

This will ignore all modifications from a new thread, except $GLOBALS['CACHE'],
which will merge any values under $GLOBALS['CACHE'][$key] that are not present
in main thread.

NOTE: if the asynchronously invoked function has pass-by-ref parameters, at the
time of end_user_func_async() call, their values will overwrite those in the
main thread. This also applies to $this, which is essentially a special
pass-by-ref parameter.

For example,

  class A {
    var $a = 1;
    function f(&$a, $b) { $this->a = 2; $a = 2; $b = 2; }
  }
  $obj = new A;
  $a = 1;
  $b = 1;
  $h = call_user_func_async(array($obj, 'f'), $a, $b);
  end_user_func_async($h);

After the end_user_func_async() call, $obj->a and $a will be 2, but $b will
remain 1.

Check status of the async job(s):

  mixed check_user_func_async(mixed $handles, int $timeout = -1);

This function takes one or multiple handles and check whether the job(s) have
finished. If taking a single handle, it will return TRUE or FALSE to indicate
whether the job has finished. If taking an array, it will return an array of
finished jobs.

The parameter timeout is in milliseconds. If -1 is specified, it is
non-blocking. If 0 is specified, it blocks until some of the specified jobs
are done.
