Prozesssynchronisation und Shared Memory

Ich glaube, dass Sie durch das Studium der vorherigen Artikel ein gewisses Verständnis für den Prozess von Swoole haben. Ob Einzelprozess oder Prozesspool, wir konzentrieren uns auf die Kommunikation zwischen Prozessen. Schließlich sind Prozesse für Prozesse speicherisoliert, und die Kommunikation ist ein relativ großes Problem. Die Inhalte, über die wir zuvor gesprochen haben, verwenden eigentlich keine Tools von Drittanbietern für die Kommunikation, aber eine bequemere Möglichkeit besteht darin, einige Tools von Drittanbietern direkt als Zwischenspeichermedium zu verwenden, damit verschiedene Prozesse die Inhalte hier direkt lesen können die Fähigkeit zu kommunizieren. Zum Beispiel verwenden wir am häufigsten Redis, aber selbst wenn Redis verwendet wird, selbst wenn der Verbindungspool verwendet wird, gibt es einen Prozess des Verbindungsaufbaus, daher ist dies nicht der effizienteste. Heute lernen wir eine gemeinsam genutzte Speichertabelle kennen, die eine effizientere Datensynchronisierungsmethode ist, die von Swoole bereitgestellt wird. Außerdem müssen wir zwei weitere sehr gebräuchliche Interprozess-Synchronisationsfunktionen lernen, eine ist ein Lock-Free-Zähler und die andere eine Prozesssperre.

Prozesssynchronisierung

In Bezug auf das Problem der Prozesssynchronisierung haben wir es sehr früh erklärt. Es bezog sich auf globale Variablen und erklärte, warum traditionelle globale Konstanten in Swoole nicht verwendet werden können. Um ähnliche globale Funktionen zwischen Prozessen zu erreichen, gibt es neben Tabellen- oder externen Tools von Drittanbietern, die später besprochen werden, auch einige kleine Tools, die unsere Aufmerksamkeit verdienen.

Lock-Free-Zähler zwischen Prozessen (atomar)

Der prozessübergreifende Lock-Free-Zähler ist eine atomare Zähloperationsklasse, die von der untersten Schicht von Swoole bereitgestellt wird, die das lock-freie atomare Inkrement und Dekrement von Ganzzahlen leicht realisieren kann. Kommt Ihnen das Wort Atom bekannt vor? Das ist richtig, es ist diese Atomarität von ACID in der Datenbank. Ob erfolgreich oder fehlgeschlagen, atomare Operationen sind Operationen, die nicht durch Thread-Scheduling oder Multiprozess unterbrochen werden und nach dem Start bis zum Ende ausgeführt werden.

Der atomare Zähler ist eigentlich eine Zählerfunktionsanwendung mit atomarer Operationsfähigkeit, die einfach in einen gemeinsam genutzten Speicher platziert wird, der einfache Additions- und Subtraktionszuweisungsoperationen implementieren soll.

$atomic = new Swoole\Atomic();

(new \Swoole\Process(function (\Swoole\Process $worker) use ($atomic) {
   while($atomic->get() < 5){
       $atomic->add();
       echo "Atomic Now: {$atomic->get()}, pid: {$worker->pid}", PHP_EOL;
       sleep(1);
   }
   echo "Shutdown {$worker->pid}", PHP_EOL;

}))->start();

(new \Swoole\Process(function (\Swoole\Process $worker) use ($atomic) {
   while($atomic->get() < 10){
       $atomic->add();
       echo "Atomic Now: {$atomic->get()}, pid: {$worker->pid}", PHP_EOL;
       sleep(1);
   }
   echo "Shutdown {$worker->pid}", PHP_EOL;
}))->start();

\Swoole\Process::wait();
\Swoole\Process::wait();

// [root@localhost source]# php 3.6进程同步与共享内存.php
// Atomic Now: 1, pid: 1469
// Atomic Now: 2, pid: 1468
// Atomic Now: 3, pid: 1468
// Atomic Now: 4, pid: 1469
// Atomic Now: 5, pid: 1468
// Atomic Now: 6, pid: 1469
// Shutdown 1468
// Atomic Now: 7, pid: 1469
// Atomic Now: 8, pid: 1469
// Atomic Now: 9, pid: 1469
// Atomic Now: 10, pid: 1469
// Shutdown 1469

Nichts Besonderes, Sie können sich ein atomares Objekt als Int vorstellen, aber es ist kleiner als ein Int, nur eine 32-Bit-Ganzzahl ohne Vorzeichen. Wenn Sie große Zahlen benötigen, können Sie Swoole\Atomic\Long verwenden, ein 64-Bit-Ganzzahlobjekt mit Vorzeichen. Aber Objekte im Long-Format unterstützen die folgenden Operationen wait() und Weakup() nicht.

$atomic = new Swoole\Atomic();

//$atomic->cmpset(0, 1);

(new \Swoole\Process(function (\Swoole\Process $worker) use ($atomic) {
   $atomic->wait(3);
   echo "Shutdown wait Process: {$worker->pid}", PHP_EOL;

}))->start();

(new \Swoole\Process(function (\Swoole\Process $worker) use ($atomic) {
   sleep(2);
   $atomic->wakeup();
//    $atomic->cmpset(0, 1);
   echo "Shutdown other Process: {$worker->pid}", PHP_EOL;
}))->start();

\Swoole\Process::wait();
\Swoole\Process::wait();

//  [root@localhost source]# php 3.6进程同步与共享内存.php
//  Shutdown other Process: 1511
//  Shutdown wait Process: 1510

Was bedeuten diese beiden Methoden? Wenn der Wert von atomic 0 ist und wait() aufgerufen wird, beginnt es, in den Wartezustand einzutreten. Worauf wartest du? wait() endet, wenn die Methode „weakup()“ aufgerufen oder der atomare Wert auf 1 gesetzt wird. Wenn der atomare Wert am Anfang nicht 0 ist, funktioniert die Methode wait() nicht.

In diesem Testcode ist unsere endgültige Ausgabe, dass other zuerst ausgeführt wird, d. h. nach 2 Sekunden Wartezeit endet nach dem Aufrufen der Weakup()-Methode der vorherige Prozess, der intern die Wait()-Methode aufgerufen hat. Der Parameter von wait() gibt an, wie lange gewartet werden soll. Wenn er auf -1 gesetzt ist, wird ewig gewartet. Andernfalls wird entsprechend der Anzahl der Sekunden des Parameterwerts gewartet. Nach dem Timeout wird nicht gewartet und weiter laufen. Hier können Sie testen, ob atomic am Anfang auf einen Wert ungleich Null gesetzt ist, d. h. die Zeile, die die Methode cmpset() im Kommentar aufruft, eingeschaltet ist. Wenn Sie es dann erneut ausführen, werden Sie feststellen, dass wait() nicht funktioniert und der erste Prozess direkt ausgeführt wird.

Diese Funktion kann zwar eine Art Lock-Fähigkeit realisieren, ist aber nicht besonders flexibel, schließlich muss sie eine Weile warten() und eine Weile schwächen(), und vielleicht wird der Wert direkt geändert. Wir haben auch eine bequemere Funktion zum direkten Bedienen von Sperren in Swoole, nämlich der Sperre zwischen Prozessen, über die wir weiter unten sprechen werden.

Prozessübergreifende Sperre (Lock)

Sperroperationen sind sehr wichtig für Multiprozess- und Multithread-bezogene Operationen. warum? Eines der wichtigsten Probleme bei der parallelen Ausführung von Code ist, dass es möglich ist, gleichzeitig etwas zu ändern, sei es eine Datenbank, Speicherdaten oder Dateiressourcen.Das von uns verwendete MySQL selbst verfügt über verschiedene Sperrmechanismen.Threads sind Wird für die Verarbeitung verwendet, einschließlich Transaktionsverarbeitungsmechanismen, Ebenen usw., um verschiedene Probleme beim Lesen und Schreiben von Daten unter hoher Parallelität zu lösen. Danach werden wir es sorgfältig und umfassend studieren, wenn wir MySQL-bezogene Inhalte lernen. Im Programmcode ist es die häufigste Situation, dass Speicheroperationen und Dateioperationen gleichzeitig ausgeführt werden und es zu Konflikten kommt.Zum Beispiel ändern wir zwei Prozesse gleichzeitig einen Wert, ein Prozess wird auf 2 geändert, ein Prozess ist es auf 3 geändert, was ist das Endergebnis?

Diese Situation ist anhand des Geschäftsszenarios tatsächlich zu sehen: Handelt es sich um eine kumulierte Veränderung von 3? Oder repräsentiert 3 einen Zustand? In jedem Fall sollte die Verarbeitung der beiden Prozesse in Ordnung sein, und sie können nicht gleichzeitig betrieben werden.Das Ergebnis, das durch den realen gleichzeitigen Betrieb erhalten wird, istmehrdeutig und kann durch unsere Ableitung nicht vorhergesagt werden. Zu diesem Zeitpunkt können wir diese Art von Operation sperren, sodass nur ein Prozess diese Ressource gleichzeitig betreiben kann, sodass das Ergebnis deterministisch und nicht mehrdeutig ist.

$lock = new Swoole\Lock();

(new \Swoole\Process(function (\Swoole\Process $worker) use ($lock) {
    echo "Process: {$worker->pid} Wait", PHP_EOL;
    $lock->lock();
    echo "Process: {$worker->pid} Locked", microtime(true), PHP_EOL;
    sleep(3);
    $lock->unlock();
    echo "Process: {$worker->pid} exit;", PHP_EOL;
}))->start();

(new \Swoole\Process(function (\Swoole\Process $worker) use ($lock) {
    sleep(1);
    echo "Process: {$worker->pid} Wait ", PHP_EOL;
    $lock->lock();
    echo "Process: {$worker->pid} Locked",microtime(true), PHP_EOL;
    $lock->unlock();
    echo "Process: {$worker->pid} exit;", PHP_EOL;
}))->start();

\Swoole\Process::wait();
\Swoole\Process::wait();

//[root@localhost source]# php 3.6进程同步与共享内存.php
//Process: 1611 Wait
//Process: 1611 Locked1640572026.9681
//Process: 1612 Wait
//Process: 1611 exit;
//Process: 1612 Locked1640572029.9771
//Process: 1612 exit;

In diesem Testcode werden die lock()- und unlock()-Methoden des Swoole\Lock-Objekts verwendet, um die Sperre zu sperren und freizugeben. Der erste Prozess sperrt nach dem Start und ruht dann für 3 Sekunden.Der zweite Prozess will auch sperren, nachdem er hereingekommen ist, aber der erste Prozess hat die Sperre bereits hinzugefügt, also muss er warten, bis der erste Prozess die Sperre freigibt Es ist ersichtlich, dass nach 3 Sekunden der zweite Prozess die Sperre erworben hat.

Studenten, die C/C++ oder Java und Go gelernt haben, sollten das leicht verstehen.Wenn es einige IO-Ressourcenoperationen gibt, insbesondere das Schreiben von Daten, müssen sie gesperrt werden, um Verwirrung zu vermeiden, die durch mehrere Prozesse verursacht wird, die gleichzeitig Daten schreiben. Gleichzeitig können Sperren zwischen Prozessen nicht in Coroutinen verwendet werden.Versuchen Sie, in dieser Sperre keine Coroutine-bezogenen APIs zu verwenden, da es sonst  leicht zu Deadlocks kommt  .

Für weitere Informationen können Sie auf die offizielle Dokumentation verweisen und nach verwandtem Wissen suchen, um tiefer zu lernen und zu verstehen.

Shared Memory (Tabelle)

Die oben genannten Lock-Free-Zähler und Lock-Funktionen sind tatsächlich einige Funktionen, die für die gemeinsame Nutzung von Daten oder die Kommunikation zwischen Prozessen bereitgestellt werden. Beispielsweise kann der Zähler einfach durch einfaches Ansammeln von Zahlen verwendet werden, und wenn dieselbe Handle-Datei verwendet wird, wird eine Sperre hinzugefügt, und alle Prozesse in dieser Datei können ihre Daten lesen. Tatsächlich ist dies auch eine Art der Kommunikation zwischen Prozessen und der Datensynchronisierung. Darüber hinaus bietet Swoole ein Tabellentool, bei dem es sich um eine ultrahochleistungsfähige In-Memory-Datenstruktur handelt, die direkt auf der Grundlage von gemeinsam genutztem Speicher und Sperren implementiert wird. Es kann das Problem der Multiprozess-/Multithread-Datenfreigabe und Synchronisierungssperre lösen.

Es zeichnet sich durch starke Leistung, integriertes Row-Lock-Spin-Lock (kein separater Lock-Betrieb erforderlich), Unterstützung für mehrere Prozesse aus und ist ein leistungsstarkes Tool zum Teilen von Daten und zum Kommunizieren zwischen Prozessen.

$table = new Swoole\Table(1024);
$table->column('worker_id', Swoole\Table::TYPE_INT);
$table->column('count', Swoole\Table::TYPE_INT);
$table->column('data', Swoole\Table::TYPE_STRING, 64);
$table->create();

$ppid = getmypid();
$table->set($ppid, ['worker_id'=>getmypid(), 'count'=>0'data'=>"这里是 " . $ppid]);


(new \Swoole\Process(function (\Swoole\Process $worker) use ($table) {
    $table->set($worker->pid, ['worker_id'=>$worker->pid, 'count'=>0'data'=>"这里是 {$worker->pid}"]);
    sleep(1);
    $table->incr($worker->pid, 'count');
    print_r($table->get($worker->pid));
}))->start();

(new \Swoole\Process(function (\Swoole\Process $worker) use ($table, $ppid) {
    $table->set($worker->pid, ['worker_id'=>$worker->pid, 'count'=>3'data'=>"这里是 {$worker->pid}"]);
    sleep(1);
    $table->decr($worker->pid, 'count');
    print_r($table->get($worker->pid));
    sleep(1);

    echo "{$worker->pid} 内部循环:", PHP_EOL;
    foreach($table as $t){
        print_r($t);
    }
    if($table->exist($ppid)){
        $table->del($ppid);
    }
}))->start();

\Swoole\Process::wait();
\Swoole\Process::wait();

echo "Talbe 数量:",$table->count(), PHP_EOL;
echo "主进程循环:", PHP_EOL;
foreach($table as $t){
    print_r($t);
}
echo "Table 状态:", PHP_EOL;
print_r($table->stats());

Nach der Instanziierung des Swoole\Table-Objekts müssen wir die Spalteninformationen angeben. Der Instanziierungsparameter stellt die maximale Zeilenanzahl in der Tabelle dar. Diese Zeilenanzahl ist nicht unbedingt genau und hängt mit der reservierten Speichergröße zusammen. Beachten Sie, dass Speicher nicht dynamisch zugewiesen wird. Es öffnet einen festen Inhaltsraum, wenn es direkt instanziiert wird. Es ist notwendig, den benötigten Speicherplatz im Voraus zu planen. Die Operation zum Angeben einer Spalte ähnelt besonders der Operation zum Erstellen einer Datenbanktabelle.Dieser Schritt besteht darin, Daten im Speicher einfach zu serialisieren.

Dann können wir die Daten jeder Zeile mit der Methode set() setzen. In verschiedenen Prozessen werden die Daten geteilt und können eingesehen werden.

Schließlich implementiert es auch iteratorbezogene Funktionen, die von foreach() durchlaufen, von count() zurückgegeben und von stats() zurückgegeben werden können.

Die endgültige Ausgabe sollte so aussehen.

//  [root@localhost source]# php 3.6进程同步与共享内存.php
//  Array
//  (
//      [worker_id] => 1551
//      [count] => 1
//      [data] => 这里是 1551
//  )
//  Array
//  (
//      [worker_id] => 1552
//      [count] => 2
//      [data] => 这里是 1552
//  )
//  1552 内部循环:
//  Array
//  (
//      [worker_id] => 1550
//      [count] => 0
//      [data] => 这里是 1550
//  )
//  Array
//  (
//     [worker_id] => 1551
//      [count] => 1
//      [data] => 这里是 1551
//  )
//  Array
//  (
//      [worker_id] => 1552
//     [count] => 2
//     [data] => 这里是 1552
//  )
//  Talbe 数量:2
//  主进程循环:
//  Array
//  (
//      [worker_id] => 1551
//      [count] => 1
//      [data] => 这里是 1551
//  )
//  Array
//  (
//      [worker_id] => 1552
//      [count] => 2
//      [data] => 这里是 1552
//  )
//  Table 状态:
//  Array
//  (
//      [num] => 2
//      [conflict_count] => 0
//      [conflict_max_level] => 0
//      [insert_count] => 3
//      [update_count] => 2
//      [delete_count] => 1
//     [available_slice_num] => 204
//     [total_slice_num] => 204
//  )

Zusammenfassen

Der Inhalt der heutigen Studie bezieht sich auf die Interprozess-Synchronisation, mit der die Kommunikation und Synchronisation zwischen Prozessen wesentlich komfortabler wird. Allerdings sollte beachtet werden, dass wenn Atomic und Lock in Serveranwendungen verwendet werden, diese nicht in Callback-Funktionen wie onReceive angelegt werden, da sonst der Speicher weiter wachsen kann, was den legendären Memory-Leak-Überlauf darstellt. warum? Tatsächlich liegt es daran, dass sie vom Prozess global gemeinsam genutzt und nicht recycelt werden.Wenn sie die ganze Zeit erstellt werden, wird der Prozess ohne Unterbrechung weiter erstellt und schließlich den physischen Speicher der gesamten Anwendung und des Servers sprengen.

Nun, wir haben bereits den grundlegenden Inhalt des Prozesses gelernt, und dann werden wir das nächste große Kapitel betreten, nämlich das Lernen des zugehörigen Inhalts der Coroutine. Weitere aufregende Inhalte liegen bereits vor Ihnen, Sie können sie nicht verpassen, wenn Sie auf Sanlian achten!

Testcode:

https://github.com/zhangyue0503/swoole/blob/main/3.Swoole%E8%BF%9B%E7%A8%8B/source/3.6%E8%BF%9B%E7%A8%8B%E5% 90%8C%E6%AD%A5%E4%B8%8E%E5%85%B1%E4%BA%AB%E5%86%85%E5%AD%98.php

Referenzdokumentation:

https://wiki.swoole.com/#/process/process_pool

https://wiki.swoole.com/#/process/process_manager