Heim  >  Artikel  >  Backend-Entwicklung  >  Detaillierte Einführung in Kanäle in Go-Sprache

Detaillierte Einführung in Kanäle in Go-Sprache

尚
nach vorne
2019-11-25 16:29:513912Durchsuche

Detaillierte Einführung in Kanäle in Go-Sprache

In diesem Artikel geht es um den Kanal in der go-Sprache Der Grund, warum ich ihn schreibe, ist, dass ich den Kanal gleichzeitig für sehr wichtig halte , Der Kanal ist auch ein wichtiger Unterstützungspunkt für die Go-Parallelität, da Go den gemeinsam genutzten Speicher für die Nachrichtenübermittlung verwendet, anstatt den gemeinsam genutzten Speicher für die Kommunikation zu verwenden.

Die gleichzeitige Programmierung ist sehr gut, aber die Parallelität ist sehr kompliziert. Die Schwierigkeit liegt in der Koordination. Es ist sehr wichtig, wie man mit der Kommunikation zwischen verschiedenen Programmen umgeht. Bevor wir über die Verwendung und Eigenschaften von Kanälen schreiben, müssen wir die Kommunikation zwischen Prozessen im Betriebssystem überprüfen.

Interprozesskommunikation

In der Technik gibt es zwei allgemeine Kommunikationsmodelle: gemeinsame Daten und Nachrichten. Wie der Name schon sagt, bezieht sich Prozesskommunikation auf den Informationsaustausch zwischen Prozessen, da der gegenseitige Ausschluss und die Synchronisierung von Prozessen den Informationsaustausch zwischen Prozessen erfordern. Jeder, der sich mit Betriebssystemen beschäftigt hat, weiß, dass Prozesskommunikation grob in Prozesse auf niedriger Ebene unterteilt werden kann Nun handelt es sich im Grunde genommen um fortgeschrittene Prozesskommunikation. Unter diesen können fortgeschrittene Kommunikationsmechanismen unterteilt werden in: Nachrichtenübermittlungssystem, Shared-Memory-System, Pipeline-Kommunikationssystem und Client-Server-System.

1. Nachrichtenübermittlungssystem

Es ist nicht auf einen gemeinsamen Speicherbereich oder eine bestimmte Datenstruktur angewiesen. Es nutzt das System, um formatierte Nachrichten als Einheiten bereitzustellen Primitive werden verwendet, um den Datenaustausch abzuschließen, was sehr ineffizient zu sein scheint.
2. Shared-Memory-System

Die Prozesse kommunizieren über diese Räume, beispielsweise eine bestimmte Datei als Träger .

3. Client-Server-System

Mehrere andere Kommunikationsmechanismen befinden sich grundsätzlich auf demselben Computer (in einigen Fällen kann man natürlich sagen, dass es sich um dieselbe Umgebung handelt). In den folgenden Fällen kann eine computerübergreifende Kommunikation erreicht werden. Das Client-Server-System ist anders. Nach meinem Verständnis kann es als IP-Anfrage betrachtet werden und ein Client fordert eine Verbindung zu einem Server an.

Diese Methode ist jetzt im Internet beliebter und wird häufiger verwendet, z. B. RPC (was sehr hochwertig klingt, aber schon lange im Betriebssystem vorhanden ist) und Sockets ., socket, dies wird häufig verwendet und steht in engem Zusammenhang mit unserer Programmierung, da Sie feststellen werden, dass viele Dienste RPC-Aufrufe verwenden müssen.

4. Pipeline-Kommunikationssystem

Lassen Sie uns abschließend ausführlich über den Mechanismus der Pipe-Kommunikation sprechen. Auf Betriebssystemebene werden Pipes verwendet, um einen Lesevorgang zu verknüpfen und einen Schreibprozess, der die Kommunikation zwischen ihnen ermöglicht. Auf dem System wird es als Pipe-Datei bezeichnet.

Die implementierten Mechanismen sind wie folgt: Pipes bieten die folgenden zwei Funktionen

1 Wenn ein Prozess einen Lese- oder Schreibvorgang für eine Pipe-Datei ausführt, müssen andere Prozesse warten oder blockieren oder schlafen.

2. Wenn der Schreib-(Eingabe-)Prozess die Pipe-Datei schreibt, wird sie warten oder blockieren oder schlafen, bis der Lese-(Ausgabe-)Prozess sie aufweckt, nachdem sie die Daten übernommen hat Wenn eine leere Pipe-Datei gelesen wird, wartet, blockiert oder ruht sie ebenfalls, bis der Schreibvorgang sie nach dem Schreiben in die Pipe aufweckt.

Die Verwendung des Kanals

sollte dem vierten Kanaltyp in go entsprechen. Der Kanal der Go-Sprache ist eine Kommunikationsmethode zwischen Goroutinen, die auf Sprachebene bereitgestellt wird . Es macht keinen Sinn, nur über den Kanal zu sprechen, da er zusammen mit Goroutine effektiv ist. Schauen wir uns zunächst an, wie allgemeine Sprachen den gemeinsamen Speicher zwischen Programmen lösen.

Das folgende Programm ist uns bekannt:

package main

import "fmt"

var counts int = 0

func Count() {
    counts++
    fmt.Println(counts)
}
func main() {

    for i := 0; i < 3; i++ {
        go Count()
    }
}

Jeder, der go gelernt hat, sollte den Grund kennen, denn: Das Go-Programm initialisiert die main()-Methode und das Paket und führt sie dann aus die Funktion main(), aber wenn die Funktion main() zurückkehrt, wird das Programm beendet. Das Hauptprogramm wartet nicht auf andere Goroutinen, was zu keiner Ausgabe führt.

Lassen Sie uns einen Blick darauf werfen, wie herkömmliche Sprachen dieses Parallelitätsproblem lösen:

package main

import "fmt"
import "sync"
import "runtime"

var counts int = 0

func Count(lock *sync.Mutex) {
    lock.Lock()
    counts++
    fmt.Println(counts)
    lock.Unlock()
}
func main() {
    lock := &sync.Mutex{}

    for i := 0; i < 3; i++ {
        go Count(lock)
    }

    for {
        lock.Lock()
        c := counts
        lock.Unlock()

        runtime.Gosched()

        if c >= 3 {
            break
        }

    }
}

Die Lösung ist ein bisschen komisch, indem sie eine Reihe von Sperren hinzufügt, weil ihre Ausführung so ist: Code For Die Sperrvariable muss bei jeder Operation zuerst gesperrt werden. Nach Abschluss der Operation muss die Sperre entsperrt werden. In der Hauptfunktion wird eine for-Schleife verwendet, um den Wert des Zählers kontinuierlich zu überprüfen auch gesperrt werden.

Wenn der Wert 3 erreicht, bedeutet dies, dass alle Goroutinen ausgeführt wurden. Zu diesem Zeitpunkt kehrt die Hauptfunktion zurück und das Programm wird beendet. Diese Methode ist die bevorzugte Methode zur Lösung der Parallelität. Sie können sehen, dass viele Dinge geschrieben wurden, um die Parallelität zu lösen. Wenn ein Projekt Gestalt annimmt, weiß ich nicht, wie viele Sperren hinzugefügt werden müssen.

Sehen wir uns an, wie der Kanal dieses Problem löst:

package main

import "fmt"

var counts int = 0

func Count(i int, ch chan int) {
    fmt.Println(i, "WriteStart")
    ch <- 1
    fmt.Println(i, "WriteEnd")
    fmt.Println(i, "end", "and echo", i)
    counts++
}

func main() {
    chs := make([]chan int, 3)
    for i := 0; i < 3; i++ {
        chs[i] = make(chan int)
        fmt.Println(i, "ForStart")
        go Count(i, chs[i])
        fmt.Println(i, "ForEnd")
    }

    fmt.Println("Start debug")
    for num, ch := range chs {
        fmt.Println(num, "ReadStart")
        <-ch
        fmt.Println(num, "ReadEnd")
    }

    fmt.Println("End")

    //为了使每一步数值全部打印
    for {
        if counts == 3 {
            break
        }
    }
}

Um die Schritte der Goroutine-Ausführung und die Eigenschaften des Kanals klar zu sehen, habe ich jeden Schritt speziell ausgedruckt. Das Folgende ist die Ausführung. Interessierte Studenten Sie können es selbst ausprobieren. Die Reihenfolge des Druckens kann unterschiedlich sein:

Detaillierte Einführung in Kanäle in Go-Sprache

Lassen Sie uns diesen Prozess analysieren und die Rolle des Kanals darin sehen. Das Hauptprogramm startet:

Drucken Sie „0 ForStart 0 ForEnd“, um anzuzeigen, dass i = 0 ist, diese Schleife mit der Ausführung begonnen hat und die erste Goroutine gestartet wurde;

打印 "1 ForStart"、"1 ForEnd"、"2 ForStart"、"2 ForEnd" 说明3次循环都开始,现在系统中存在3个goroutine;

打印 "Start debug",说明主程序继续往下走了,

打印 "0 ReadStar"t ,说明主程序执行到for循环,开始遍历chs,一开始遍历第一个,但是因为此时 i = 0 的channel为空,所以该channel的Read操作阻塞;

打印 "2 WriteStart",说明第一个 i = 2 的goroutine先执行到Count方法,准备写入channel,因为主程序读取 i = 0 的channel的操作再阻塞中,所以 i = 2的channel的读取操作没有执行,现在i = 2 的goroutine 写入channel后下面的操作阻塞;

打印 "0 WriteEnd",说明 i = 0 的goroutine也执行到Count方法,准备写入channel,此时主程序 i = 0 的channel的读取操作被唤醒;

打印 "0 WriteEnd" 和 "0 end and echo 0" 说明写入成功;

打印 "0 ReadEnd",说明唤醒的 i = 0 的channel的读取操作已经唤醒,并且读取了这个channel的数据;

打印 "0 ReadEnd",说明这个读取操作结束;

打印 "1 ReadStart",说明 i = 1 的channel读取操作开始,因为i = 1 的channel没有内容,这个读取操作只能阻塞;

打印 "1 WriteStart",说明 i = 1 的goroutine 执行到Count方法,开始写入channel 此时 i = 1的channel读取操作被唤醒;

打印 "1 WriteEnd" 和 "1 end and echo 1" 说明 i = 1 的channel写入操作完成;

打印 "1 ReadEnd",说明 i = 1 的读取操作完成;

打印 "2 ReadStart",说明 i = 2 的channel的读取操作开始,因为之前已经执行到 i = 2 的goroutine写入channel操作,只是阻塞了,现在因为读取操作的进行,i = 2的写入操作流程继续执行;

打印 "2 ReadEnd",说明 i = 2 的channel读取操作完成;

打印 "End" 说明主程序结束。

此时可能你会有疑问,i = 2 的goroutine还没有结束,主程序为啥就结束了,这正好印证了我们开始的时候说的,主程序是不等待非主程序完成的,所以按照正常的流程我们看不到 i = 2 的goroutine的的完全结束,这里为了看到他的结束我特意加了一个 counts 计算器,只有等到计算器等于3的时候才结束主程序,接着就出现了打印 "2 WriteEnd" 和 "2 end and echo 2"  到此所有的程序结束,这就是goroutine在channel作用下的执行流程。

上面分析写的的比较详细,耐心看两遍基本上就明白了,主要帮助大家理解channel的写入阻塞和读入阻塞的应用。

基本语法

channel的基本语法比较简单, 一般的声明格式是:

var ch chan ElementType

定义格式如下:

ch := make(chan int)

还有一个最常用的就是写入和读出,当你向channel写入数据时会导致程序阻塞,直到有其他goroutine从这个channel中读取数据,同理如果channel之前没有写入过数据,那么从channel中读取数据也会导致程序阻塞,直到这个channel中被写入了数据为止

ch <- value    //写入
value := <-ch  //读取

关闭channel

close(ch)

判断channel是否关闭(利用多返回值的方式):

 b, status := <-ch

带缓冲的channel,说起来也容易,之前我们使用的都是不带缓冲的channel,这种方法适用于单个数据的情况,对于大量的数据不太实用,在调用make()的时候将缓冲区大小作为第二个参数传入就可以创建缓冲的channel,即使没有读取方,写入方也可以一直往channel里写入,在缓冲区被填完之前都不会阻塞。

c := make(chan int, 1024)

单项channel,单向channel只能用于写入或者读取数据。channel本身必然是同时支持读写的,否则根本没法用。所谓的单向channel概念,其实只是对channel的一种使用限制。单向channel变量的声明:

var ch1 chan int   // ch1是一个正常的channel
var ch2 <-chan int // ch2是单向channel,只用于读取int数据

单项channel的初始化

ch3 := make(chan int)
ch4 := <-chan int(ch3) // ch4是一个单向的读取channel

超时机制

超时机制其实也是channel的错误处理,channel固然好用,但是有时难免会出现实用错误,当是读取channel的时候发现channel为空,如果没有错误处理,像这种情况就会使整个goroutine锁死了,无法运行。

我找了好多资料和说法,channel 并没有处理超时的方法,但是可以利用其它方法间接的处理这个问题,可以使用select机制处理,select的特点比较明显,只要有一个case完成了程序就会往下运行,利用这种方法,可以实现channel的超时处理:

原理如下:我们可以先定义一个channel,在一个方法中对这个channel进行写入操作,但是这个写入操作比较特殊,比如我们控制5s之后写入到这个channel中,这5s时间就是其他channel的超时时间,这样的话5s以后如果还有channel在执行,可以判断为超时,这是channel写入了内容,select检测到有内容就会执行这个case,然后程序就会顺利往下走了。

实现如下:

timeout := make(chan bool, 1)
go func() {
    time.Sleep(5s) // 等待s秒钟
    timeout <- true
}()

select {
    case <-ch:
    // 从ch中读取到数据
    case <-timeout:
    // 没有从ch中读取到数据,但从timeout中读取到了数据
}

推荐:go语言教程

Das obige ist der detaillierte Inhalt vonDetaillierte Einführung in Kanäle in Go-Sprache. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:cnblogs.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen