krakjoe / pthreads

Threading for PHP - Share Nothing, Do Everything :)

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Unclear on what to use instead of isWaiting()

Furious-George opened this issue · comments

This is not an issue with the code as much as potentially with documentation. I saw an issue from a couple of years ago when isWaiting() was removed, that was resolved per the following:

It was removed because there is a flaw in the logic of a program that tries to determine if another object is waiting; Because of the way waiting actually works, a context may detect that an object is waiting while the object is concurrently awoken.

Write code that does not need to make these kind of checks, it was a very bad API

I have a script that needs to be broken up into several scripts that run in parallel. Part of the reason it needs to be broken up is because it communicates a series of devices which may fail, and I don't want that to stop the whole show. I'd rather run a thread per device.

In the script nothing happens most of the time, but rarely, perhaps once per week per device, I need to make an API call.

I'm investigating different solutions, to resolve this, and I'm currently looking at pthreads.

I could start a few threads, and simply do nothing in the parent, but that is probably not a better solution than creating systemd instances out of my existing systemd service.

Similarly, I could start all the threads, just assume that they are waiting, and gather results as they come. The problem with this is that it doesn't solve the problem of hardware failure. If a device fails the parent will always be waiting for it in order to continue.

This brought me to the idea using a property of the child thread to alert the parent. Here's what I have so far:

<?php	
	class SleepThread extends Thread
	{
		public $hit = false;

		public function __construct($nap, $n)
		{
			$this->nap = $nap;
			$this->thread_no = $n;
		}

		public function run()
		{
			while (1)
			{
				$r = mt_rand(0, 9);
				if ($r == 9)
				{
					$this->hit = true;
					$this->synchronized
					(
						function () 
						{
							$this->obj = new stdClass;
							$this->obj->message = "Test\n";
							$this->result = 'Found in thread ' .  $this->thread_no . ' at ' . time() . ".  You're a thread master...";
							$this->wait();
							$this->hit = false;
							sleep(3 * $this->nap);
						}
					);
				}
				sleep($this->nap);
			}
		}
	}
	
	$thread[0] = new SleepThread(5, "0");
	$thread[1] = new SleepThread(5, "1");
	$thread[2] = new SleepThread(5, "2");
	foreach ($thread as $job)
	{
		$job->start();
	}
	
	while (1)
	{
		sleep(1);
		foreach ($thread as $job)
		{
			if ($job->hit)
			{
				$job->synchronized
				(
					function ($job) 
					{
						$job->notify();
						echo "PARENT:\t" . $job->result . "  <----That's the result.\n";
						// echo "PARENT:  " . $job->obj->message . "^^^Getting object created in child thread doesn't work for some reasons^^^.\n";
					}, 
					$job
				);
			}
		}
	}
/*
PARENT: Found in thread 2 at 1542168674.  You're a thread master...  <----That's the result.
PARENT: Found in thread 1 at 1542168684.  You're a thread master...  <----That's the result.
*/
?>

So, in lieu of isWaiting(), I use a property of the child to check if the thread is waiting from the parent. This works, but I'm not sure if it is a bad practice, or if I am otherwise doing it wrong.

Alternatively, I don't see a way to do it without constantly blocking the parent, which is what I wanted to avoid to begin with. E.g. what if a device fails, and the parent is blocked while waiting to find out if the child is waiting.

I can see myself adding more threads to this script in the future, but those would be for making the api call or writing to the db in lieu of the parent or the aforementioned child thread type, and would not need to communicate with each other. I figure the onus is on me to make sure that the hit->property is always accurate, which in my case it would be.

So, in summation, am I doing it right?

Thanks in advance.

P.S. Since I'm here, I was hoping someone could explain to me why I cannot create an object in my child and pass it to my parent, while I can retrieve data from arrays. If it were a matter of synchronization and consistency, then should we be able to create a new object, and copy to it the properties of the object created in the child automatically? I suppose this can already be achieved in a semi-automated way anyway using json_encode/_decode, so perhaps there is a way to do it by design.

commented

It would be easier to read if you reduced indentation by a level.

It looks like you have the same boat of bananas I've got. You actually need to notify the parent.

The problem is that the default thread can't be accessed (I even tried hacking it putting it's id into a serialized string and unserializing). I seem to find that you tend to often need at least one additional thread for multiplexing. I'd be far happier to just have a mutex.

Usually your parent wants something like:

$child = new Child($this);

$whatever = $this->syncronized(function() {
    if(count($this->items) === 0)
        $this->wait();

    $items = $this->items;
    $this->items = [];
    return $items;
});

Then in your child:

$this->parent->syncronized(function() use($whatever) {
    $this->parent->items[] = $whatever;
    $this->parent->notify();
});

With multiple waits and notifies it sometimes only works as ping pong or with a switch statement mapping to what to do next.

Basically try making a SleepCollectionThread or something as the parent and putting the second block of code in that, then telling each child of that parent so they can ping pong each other, that's really what syncronized means. Each has to tell the other now it's your turn sort of.

I'm not sure what the intended use really is and as far as I know your example might not work without Volatile.

I've seen some nasty things in examples doing things a bit differently. Instead using worker to take units of work. Though that's overhead heavy as it wants to pass a whole new class each time.

For efficiency it also seems difficult to avoid notify being unclear as to what they're notifying. I'm not sure if my example above would even work but it's kind of the gist of children feeding their parent just using syncronized to wrap a return stack with a mutex.

commented

You might also want to try Worker::collect. I'm trying to avoid it though because it looks like a lot of overhead. You also need volatile I believe to update those properties except it's not clear at all how to use Volatile given you can't call start on it.

@joeyhub you can use Threaded objects for synchronization without the need for a main thread context. The main thread can wait on a threaded object while another thread notifies it.

As far as Worker::collect() goes, it'll only tell you the number of tasks that were completed, not the number that are currently running. There is Worker::getStacked() also, but this doesn't include the currently-executing task. This problem led to a lot of extra code in my projects when selecting workers based on load.