使用pthreads的PHP中的竞赛条件



我有一些小代码演示如何在多线程PHP中执行竞争条件。

这个想法是我和我的朋友一起做饭。如果锅里已经有配料了,那么锅就不能煮了。

等级锅:

class Pot
{
public $id;
function __construct()
{
$this->id = rand();
}
public $ingredient;
public function cook($ingredient, $who, $time){
if ($this->ingredient==null){
$this->ingredient = $ingredient;
print "pot".$this->id.'/'.$who." cooking ".$this->ingredient. " time spent: ".$time." n";
sleep($time);
print "pot".$this->id.'/'.$who." had flush ingredient n";
$this->ingredient = null;

}else{
throw new Exception("Pot still cook ".$this->ingredient);
}
}
}

类友:

class Friend extends Thread
{
/**
* @var Pot
*/
protected $pot;
function run() {
Cocking::cleanVegetable("Friend");
print "Friend will cook: n";
$this->pot->cook("vegetable", 'Friend',4);
Cocking::digVegetable("Friend");
}
public function __construct($pot)
{
$this->pot = $pot;
}
}

我的分类:

class My
{
/**
* @var Pot
*/
private $pot;
public function doMyJob(){
Cocking::cleanRice("I");
print "I will cook: n";
$this->pot->cook("rice", "I",10);

Cocking::digRice("I");
}
public function playGame(Friend $friend){
print "play with friend n";
}
public function __construct($pot)
{
$this->pot = $pot;
}
}

类烹饪:

<?php

class Cocking
{
static function cleanRice($who){
print $who." is cleaning rice n";
}
static function cleanVegetable($who){
print $who."is cleaning vegetable n";
}

static function digRice($who){
print $who." is digging rice n";
}
static function digVegetable($who){
print $who." is digging vegetable n";
}
}

运行脚本:

require_once "Friend.php";
require_once "My.php";
require_once "Cocking.php";
require_once "Pot.php";
$pot = new Pot();
$friend = new Friend($pot);
$my = new My($pot);
$friend->start();
$my->doMyJob();
$friend->join();
$my->playGame($friend);

这是如此的扭曲以至于输出永远不会抛出异常?我认为这种情况总是会发生。

root@e03ed8b56f21:/app/RealLive# php index.php
Friendis cleaning vegetable
I is cleaning rice
Friend will cook:
I will cook:
pot926057642/I cooking rice time spent: 10
pot926057642/Friend cooking vegetable time spent: 4
pot926057642/Friend had flush ingredient
Friend is digging vegetable
pot926057642/I had flush ingredient
I is digging rice
play with friend

Pot已经被我用过了,但我的朋友仍然可以用它来做饭。那太变态了?我预计结果是:

Friend will cook:
I will cook:
pot926057642/I cooking rice time spent: 10
PHP Fatal error:  Uncaught Exception: Pot still cook rice in /app/RealLive/Pot.php:23
Stack trace:
#0 /app/RealLive/My.php(14): Pot->cook('rice', 'I', 10)
#1 /app/RealLive/index.php(12): My->doMyJob()
#2 {main}
thrown in /app/RealLive/Pot.php on line 23

ps:我的env是

PHP 7.0.10 (cli) (built: Apr 30 2019 21:14:24) ( ZTS )
Copyright (c) 1997-2016 The PHP Group
Zend Engine v3.0.0, Copyright (c) 1998-2016 Zend Technologies

非常感谢您的评论。

您的假设似乎是,您的if条件后面跟着一个直接成员分配总是需要一次性运行。然而,Friend完全有可能在线程中运行这行代码:

if ($this->ingredient==null){

并得出继续的结论,但在它到达分配$this->ingredient的下一行之前,执行切换回My/主线程,在那里它也到达这一行:

if ($this->ingredient==null){

由于Friend已经通过了if,但还没有开始实际分配成分,因此My现在也可以通过内部。无论接下来运行什么都无关紧要,现在您可以同时访问锅中烹饪的两个线程。

附加更正/注意:由于$this->ingredient不是Volatile,因此该示例似乎也不起作用。然而,这仍然会使它容易出现高于比赛条件的情况,因此仍然是一个坏主意。

如何正确执行:您确实需要使用互斥或同步部分来进行正确的同步。此外,从不假设线程不能在任何位置的中间切换,包括任何两行,如if后面跟着一个变量assign,该变量assign是一对。

以下是关于synchronized部分的PHP文档:https://www.php.net/manual/en/threaded.synchronized.php

在多线程应用程序中读取和写入变量并不能保证同步,您需要一些同步机制,该变量应该声明为原子变量,以确保一次只有一个线程可以访问它进行读取或写入,以保证两个线程之间的一致性,或者使用互斥来同步共享资源之间的访问(lock/trylock/unlock(。

目前的情况是,两个线程并行运行,成分变量取随机值​​取决于执行顺序,并且当最长的睡眠结束时,应用程序退出。

在下面的例子中,我使用了flock,这是在多个进程之间同步访问的最简单的系统之一,在测试期间,我遇到了问题,因为Friend构造函数可能没有在同一个线程中执行,而不是在同一实例的run函数中执行。。。有很多因素需要考虑,php中的Thread在我看来似乎不受欢迎,而且与语言相比,实现有点复杂​​如C.

class Friend extends Thread
{
protected $pot;
function run() {
$this->pot->cook("vegetable", 'Friend',2);
}
public function __construct($pot)
{
$this->pot = $pot;
}
}

class Pot
{
public $id;
public $ingredient;
function __construct()
{
$this->id = rand();
}
public function cook($ingredient, $who, $time)
{
$fp = fopen('/tmp/.flock.pot', 'r');
if (flock($fp, LOCK_EX|LOCK_NB)) {
if ($this->ingredient==null){
$this->ingredient = $ingredient;
print "pot".$this->id.'/'.$who." cooking ".$this->ingredient. " time spent: ".$time." n";
sleep($time);
print "pot".$this->id.'/'.$who." had flush ingredient n";
$this->ingredient = null;
}
flock($fp, LOCK_UN);
} else {
// throw new Exception("Pot still cook ".$this->ingredient);
print "ingredient busy for {$this->id}/$whon";
}
fclose($fp);
}
}
class My
{
private $pot;
public function run(){
$this->pot->cook("rice", "I",3);
}
public function __construct($pot)
{
$this->pot = $pot;
}
}
touch('/tmp/.flock.pot');
$pot = new Pot();
$friend = new Friend($pot);
$my = new My($pot);
$friend->start();
sleep(1); // try comment me
$my->run();
$friend->join();
unlink('/tmp/.flock.pot');

程序的每个线程都有自己的内存。在这个例子中它是CCD_ 12并且被保存在主存储器中。并且线程之一已经读取&更改了它,更改后的内容不会反映到主存储器中

所以其他线程看不到这种变化。所以我们应该使CCD_ 13扩展CCD_。

或者使块同步:

if ($this->ingredient==null)
$this->ingredient = $ingredient;

最新更新