使用 pthreads PHP 中的竞争条件
race condition in PHP using pthreads
我有一些小代码演示了如何在多线程中执行竞争条件 PHP。
想法是我和我的朋友共用一个锅做饭。如果锅里已经有食材了,就不能煮了。
class锅:
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朋友:
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 我的:
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;
}
}
class 上弦:
<?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: 我的环境是
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
运行s线程中的这行代码:
if ($this->ingredient==null){
... 并继续前进,但在它到达分配 $this->ingredient
的下一行之前,执行切换回 My
/主线程,它也到达这一行:
if ($this->ingredient==null){
并且由于Friend
已经通过了if
但还没有进行实际分配成分,My
现在也可以通过了。接下来的 运行 无关紧要,您现在可以让两个线程同时访问烹饪锅。
附加 correction/note: 似乎该示例也不起作用,因为 $this->ingredient
不是 Volatile
。然而,这仍然会使它容易出现上述竞争条件,因此仍然是一个坏主意。
如何正确地做到这一点:您确实需要使用互斥体或同步部分来进行正确的同步。此外,永远不会 假设线程不能在任何地方的中间切换,包括任何两行,如 if
后跟一个作为一对的变量赋值。
这是关于同步部分的 PHP 文档:https://www.php.net/manual/en/threaded.synchronized.php
在多线程应用中读写变量并不能保证同步,需要某种同步机制,变量应该声明为原子的,保证一次只有一个线程可以访问它进行读写,以保证两个线程之间的一致性,或者使用互斥锁来同步共享资源之间的访问(lock / trylock / unlock)。
目前的情况是两个线程运行并行,成分变量根据执行顺序取随机值,最长休眠结束时应用程序退出。
在下面的示例中,我使用了 flock,它是在多个进程之间同步访问的最简单的系统之一,在测试期间我遇到了问题,因为 Friend 构造函数可能与 运行 相同实例的功能...有很多因素需要考虑,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}/$who\n";
}
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');
程序的每个线程都有自己的内存。在此示例中,它是 Pot
并保存在主内存中。并且其中一个线程已经读取并更改了它,并且更改不会反映到主内存中,
所以其他线程看不到更改。
所以我们应该 Pot
extends Volatile
使改变的 can 反映到主内存。
或者使块同步:
if ($this->ingredient==null)
$this->ingredient = $ingredient;
我有一些小代码演示了如何在多线程中执行竞争条件 PHP。
想法是我和我的朋友共用一个锅做饭。如果锅里已经有食材了,就不能煮了。
class锅:
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朋友:
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 我的:
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;
}
}
class 上弦:
<?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: 我的环境是
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
运行s线程中的这行代码:
if ($this->ingredient==null){
... 并继续前进,但在它到达分配 $this->ingredient
的下一行之前,执行切换回 My
/主线程,它也到达这一行:
if ($this->ingredient==null){
并且由于Friend
已经通过了if
但还没有进行实际分配成分,My
现在也可以通过了。接下来的 运行 无关紧要,您现在可以让两个线程同时访问烹饪锅。
附加 correction/note: 似乎该示例也不起作用,因为 $this->ingredient
不是 Volatile
。然而,这仍然会使它容易出现上述竞争条件,因此仍然是一个坏主意。
如何正确地做到这一点:您确实需要使用互斥体或同步部分来进行正确的同步。此外,永远不会 假设线程不能在任何地方的中间切换,包括任何两行,如 if
后跟一个作为一对的变量赋值。
这是关于同步部分的 PHP 文档:https://www.php.net/manual/en/threaded.synchronized.php
在多线程应用中读写变量并不能保证同步,需要某种同步机制,变量应该声明为原子的,保证一次只有一个线程可以访问它进行读写,以保证两个线程之间的一致性,或者使用互斥锁来同步共享资源之间的访问(lock / trylock / unlock)。
目前的情况是两个线程运行并行,成分变量根据执行顺序取随机值,最长休眠结束时应用程序退出。
在下面的示例中,我使用了 flock,它是在多个进程之间同步访问的最简单的系统之一,在测试期间我遇到了问题,因为 Friend 构造函数可能与 运行 相同实例的功能...有很多因素需要考虑,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}/$who\n";
}
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');
程序的每个线程都有自己的内存。在此示例中,它是 Pot
并保存在主内存中。并且其中一个线程已经读取并更改了它,并且更改不会反映到主内存中,
所以其他线程看不到更改。
所以我们应该 Pot
extends Volatile
使改变的 can 反映到主内存。
或者使块同步:
if ($this->ingredient==null)
$this->ingredient = $ingredient;