2013-01-28 29 views
8

Làm việc gần đây với phần mở rộng pthreads, tôi đã phát hiện ra một sự bất thường. Tôi có một đối tượng đơn giản với một trạng thái nội bộ:pthread Các đối tượng chủ đề đặt lại trạng thái của chúng

class Sum { 
    private $value = 0; 
    public function add($inc) { $this->value += $inc; } 
    public function getValue() { return $this->value; } 
} 

Bây giờ tôi đã tạo ra một lớp Thread rằng làm điều gì đó với đối tượng này:

class MyThread extends Thread { 
    private $sum; 

    public function __construct(Sum $sum) { 
     $this->sum = $sum; 
    } 

    public function run(){ 
     for ($i=0; $i < 10; $i++) { 
      $this->sum->add(5); 
      echo $this->sum->getValue() . " "; 
     } 
    } 
} 

Trong chức năng chính của tôi đã tạo ra một đối tượng Sum, tiêm nó vào sợi và bắt đầu nó:

$sum = new Sum(); 
$thread = new MyThread($sum); 
$thread->start(); 
$thread->join(); 
echo $sum->getValue(); 

tôi mong đợi kết quả được 50, bởi vì các chủ đề đã phải tăng giá trị gấp 10 lần bởi 5. Nhưng tôi đã nhận một 0!

Ngạc nhiên hơn là nó không đồng bộ trở lại vào chủ đề chính không thành công nhưng chủ đề dường như quên trạng thái nội bộ của nó trên đường: Đầu ra của tiếng vọng bên trong phương thức run() không phải là mong đợi 5 10 15 20 25 30 35 40 45 50 nhưng 0 0 0 0 0 0 0 0 0 0. Không ai can thiệp vào chủ đề - tại sao nó không bảo toàn trạng thái của nó?


Side lưu ý: Nếu tôi không bắt đầu thread nhưng thay vì gọi run() - Phương pháp trực tiếp trong các chủ đề chính ($thread->run();), kết quả là vẫn như nhau. Nhưng nếu bây giờ tôi xóa extends Thread trong tuyên bố lớp học, nó hoạt động hoàn hảo và trả về dự kiến ​​5 10 15 20 25 30 35 40 45 50.

Trả lời

17

Bất kỳ đối tượng không có nguồn gốc từ định nghĩa pthreads sẽ được tuần tự hóa khi thiết lập nó là một thành viên của một đối tượng có nguồn gốc từ pthreads.

Thao tác như + = và [] sử dụng con trỏ trong nội bộ, việc tuần tự hóa không tương thích với con trỏ cho các đối tượng khác. Trong cuốn hướng dẫn trên trang giới thiệu, nó khẳng định rằng bất kỳ đối tượng dự định sẽ được điều khiển bởi nhiều ngữ cảnh nên mở rộng Stackable, Chủ đề hoặc Worker, như

<?php 
class Sum extends Stackable { 
    private $value = 0; 
    public function add($inc) { $this->value += $inc; } 
    public function getValue() { return $this->value; } 
    public function run(){} 
} 

class MyThread extends Thread { 
    public $sum; 

    public function __construct(Sum $sum) { 
     $this->sum = $sum; 
    } 

    public function run(){ 
     for ($i=0; $i < 10; $i++) { 
      $this->sum->add(5); 
      echo $this->sum->getValue() . " "; 
     } 
    } 
} 

$sum = new Sum(); 
$thread = new MyThread($sum); 
$thread->start(); 
$thread->join(); 
echo $sum->getValue(); 
?> 

Nếu Sum không được sử dụng con trỏ bạn sẽ có tùy chọn thu hồi tham chiếu từ đối tượng luồng sau khi tham gia.

Đây là những thao tác đơn giản, bạn không bắt buộc phải đồng bộ hóa. Thời gian duy nhất bạn nên đồng bộ hóa là khi bạn dự định chờ một đối tượng hoặc thông báo cho một đối tượng.

Các đối tượng đi kèm với pthreads rất phù hợp hơn với môi trường này và không bao giờ được tuần tự hóa.

Xin đừng đọc giới thiệu trong cuốn hướng dẫn và tất cả các ví dụ trong những phương pháp bạn muốn sử dụng để tìm hiểu chính xác những gì là gì, sau đó cảm thấy tự do để hỏi tại sao :)

tôi biết rằng người dùng của PHP aren Nếu chúng ta phải nghiên cứu, nhưng chúng ta đang đẩy phong bì ở đây, bạn sẽ thấy có những cách chính xác để làm mọi thứ một cách không chính xác, hầu hết chúng được ghi lại trong các ví dụ, và bất cứ điều gì mà tôi không chắc chắn sẽ được trích xuất từ tôi trên SO và cuối cùng tìm đường đến tài liệu.

Tôi không chắc chắn nếu ví dụ bạn đã được thử nghiệm trên đối tượng đặc biệt, nhưng mã mà bạn cung cấp không cần phải hai đối tượng, và không nên hai đối tượng trong hai, hãy xem xét những điều sau đây:

<?php 
class MyThread extends Thread { 
    public $sum; 

    public function run(){ 
     for ($i=0; $i < 10; $i++) { 
      $this->add(5); 

      printf("%d ", $this->sum); 
     } 
    } 

    public function add($num) { $this->sum += $num; } 
    public function getValue() { return $this->sum; } 
} 

$thread = new MyThread(); 
$thread->start(); 
$thread->join(); 
var_dump($thread->getValue()); 
?> 

Nó có thể hữu ích để bạn có thể xem các tính năng một vài chi tiết trong hành động với một lời giải thích, vì vậy ở đây đi, đây là một ví dụ tương tự như của bạn:

<?php 
class MyThread extends Thread { 
    public $sum; 

    public function __construct() { 
     $this->sum = 0; 
    } 

    public function run(){ 
     for ($i=0; $i < 10; $i++) { 
      $this->add(5); 

      $this->writeOut("[%d]: %d\n", $i, $this->sum); 
     } 

     $this->synchronized(function($thread){ 
      $thread->writeOut("Sending notification to Process from %s #%lu ...\n", __CLASS__, $thread->getThreadId()); 
      $thread->notify(); 
     }, $this); 
    } 

    public function add($num) { $this->sum += $num; } 
    public function getValue() { return $this->sum; } 

    /* when two threads attempt to write standard output the output will be jumbled */ 
    /* this is a good use of protecting a method so that 
     only one context can write stdout and you can make sense of the output */ 
    protected function writeOut($format, $args = null) { 
     $args = func_get_args(); 
     if ($args) { 
      vprintf(array_shift($args), $args); 
     } 
    } 
} 

$thread = new MyThread(); 
$thread->start(); 

/* so this is synchronization, rather than joining, which requires an actual join of the underlying thread */ 
/* you can wait for notification that the thread is done what you started it to do */ 
/* in these simple tests the time difference may not be apparent, but in real complex objects from */ 
/* contexts populated with more than 1 object having executed many instructions the difference may become very real */ 
$thread->synchronized(function($thread){ 
    if ($thread->getValue()!=50) { 
     $thread->writeOut("Waiting for thread ...\n"); 
     /* you should only ever wait _for_ something */ 
     $thread->wait(); 
     $thread->writeOut("Process recieved notification from Thread ...\n"); 
    } 
}, $thread); 

var_dump($thread->getValue()); 
?> 

này kết hợp một số tính năng nâng cao hơn trong một số ví dụ đơn giản và được nhận xét để giúp bạn. Về chủ đề chia sẻ các đối tượng, không có gì sai khi truyền xung quanh một đối tượng Thread nếu nó chứa một số chức năng và dữ liệu cần thiết trong các chủ đề khác hoặc stackables. Bạn nên cố gắng sử dụng càng ít chủ đề và đối tượng càng tốt để hoàn thành công việc.

+0

Có mã đó trong một lớp chính xác là những gì tôi đã đề xuất cho mục đích thử nghiệm. Tôi nghĩ anh ấy cần hai lớp vì anh ấy muốn đếm từ nhiều chủ đề. – th3falc0n

+0

Tôi đã mở rộng câu trả lời để trang trải khả năng này ... sử dụng càng ít càng tốt là thứ tự của ngày khi làm việc với đa luồng.Một thước đo hiệu quả không phải là số lượng đề tài mà ứng dụng của bạn sử dụng, nhưng nó sử dụng số lượng chủ đề ít nhất có thể để hoàn thành công việc trong thời gian ngắn nhất như thế nào. –

+0

Một từ đúng. Threading thực sự là một bước đi trên các cạnh. – th3falc0n

1

Vấn đề của bạn là bạn đang truy cập biến từ chuỗi chính và từ chuỗi Chủ đề của tôi. CPU lưu trữ biến và nó được cập nhật trong bộ nhớ cache cho MyThread nhưng không phải trong bộ đệm cho chủ đề chính, vì vậy cả hai luồng của bạn không bao giờ thấy các thay đổi luồng khác. Trong Java/C vv có từ khóa dễ bay hơi nhưng tôi không biết nếu có tồn tại trong PHP.

Tôi nghĩ bạn nên cố gắng để gọi các phương pháp trong tổng đồng bộ (http://php.net/manual/en/thread.synchronized.php)

Ví dụ, thay vì:

 $this->sum->add(5); 

Gọi:

 $this->synchronized(function($thread){ 
      $thread->sum->add(5); 
     }, $this); 
+0

nhưng chuỗi thậm chí không thấy các thay đổi của riêng nó ... –

+0

không phải do bạn xác định $ sum = new Sum() bên ngoài chuỗi sao cho $ sum-> giá trị thuộc về chuỗi chính. – th3falc0n

+0

Thay vì truyền số tiền cho chuỗi, bạn có thể thử tạo đối tượng Sum bên trong chuỗi. Nếu điều đó làm việc tôi đúng nếu không, bạn cũng biết;) – th3falc0n

0

Tôi nghĩ tôi cũng đang làm điều gì đó sai. Như đã đề cập [] và các thao tác khác sử dụng con trỏ sẽ không hoạt động. Đây là giải pháp thay thế:

class MyThread extends Thread { 
    public $table; 

    public function __construct($data) { 
     $this->table= $data; 
    } 

    public function run(){ 
     //Set temporary array with current data 
     $temp = $this->table; 
     for ($i=0; $i < 10; $i++) { 
      //Add new elements to the array 
      $temp[] = $i . ' New Value'; 
     } 
     //Overwrite class variable with temporary array 
     $this->table = $temp; 
    } 
} 

Điều này hoạt động khi chúng tôi đang sử dụng [] hoạt động trên biến chức năng tạm thời và chúng tôi đặt biến này làm biến lớp.

Better dụ tại sao nó cần thiết:

class MyThread extends Thread { 
     public $table; 

     public function __construct($data) { 
      $this->table= $data; 
     } 

     public function run(){ 
      $this->addElem(); 
      $temp = $this->table; 
      $temp[] = 1; 
      $this->table = $temp; 
     } 

     protected function addElem() { 
      $temp = $this->table; 
      $temp[] = $i . ' New Value'; 
      $this->table = $temp; 
     } 
    } 

Vì vậy, chúng ta có thể sử dụng và thêm yếu tố mới vào biến trong nhiều hơn một chức năng. Điều tương tự cũng sẽ làm việc cho + = và các toán tử khác, nhưng bạn cần biến tạm thời cho nó.

dễ dàng để mở rộng chức năng cho việc này:

protected function overload($name, $val, $type = '[]'){ 
     $temp = $this->$name; 
     switch($type){ 
      case '[]': 
       $temp[] = $val; 
       break; 
      case '+=': 
       $temp += $val; 
       break; 
      case '.=': 
       $temp .= $val; 
       break; 
     } 
     $this->$name = $temp; 
    } 

Eval có thể làm việc ở đây nhưng tôi tìm thấy điều này an toàn hơn nhiều.

Các vấn đề liên quan