Heim  >  Artikel  >  Backend-Entwicklung  >  Ist Single-Threading eine Funktion der Go-Sprache?

Ist Single-Threading eine Funktion der Go-Sprache?

青灯夜游
青灯夜游Original
2023-01-06 11:17:193328Durchsuche

Single-Threading ist keine Funktion der Go-Sprache, Go-Sprache ist Multithreading. Das Thread-Modell von Golang ist das MPG-Modell. Insgesamt weisen Go-Prozesse und Kernel-Threads eine Viele-zu-Viele-Entsprechung auf, sodass Go im Multi-Thread-Modus sein muss und Kernel-Threads 1 zu 1 entsprechen und mehrere Gs mehreren M entsprechen Dementsprechend bezieht sich P auf Kontextressourcen.

Ist Single-Threading eine Funktion der Go-Sprache?

Die Betriebsumgebung dieses Tutorials: Windows 7-System, GO Version 1.18, Dell G3-Computer.

Single-Threading ist keine Funktion der Go-Sprache, Go-Sprache ist Multithreading. Wenn es Single-Threaded ist, warum ist es Ihrer Meinung nach eine Sprache, die für hohe Parallelität im Multi-Core-Zeitalter geboren wurde?

Golangs Thread-Modell ist das MPG-Modell. Insgesamt weisen Go-Prozesse und Kernel-Threads eine Viele-zu-Viele-Entsprechung auf, daher müssen sie zunächst multithreaded sein. Unter diesen entspricht M dem Kernel-Thread 1:1, und mehrere G entsprechen mehreren M. P bezieht sich auf die Kontextressource, es gibt nicht viel zu sagen. Wann erhöht sich die Anzahl der M (oder Kernel-Threads)? Das heißt, wenn die aktuelle Anzahl von M nicht geplant werden kann, um alle aktuellen G zu verschieben, wird ein neues M zur Verarbeitung verwendet.

Go-Parallelität (Multithreading)

Einige Leute vergleichen Go mit der C-Sprache im 21. Jahrhundert. Der erste Grund liegt darin, dass die Go-Sprache einfach im Design ist. Der zweite Grund ist, dass sie im 21. Jahrhundert am wichtigsten ist ist parallele Programmierung und Go ab der Sprachebene Parallelität wird unterstützt.

goroutine

goroutine ist der Kern des parallelen Designs von Go. Letztlich ist Goroutine tatsächlich ein Thread, aber er ist kleiner als ein Thread. Die Go-Sprache hilft Ihnen dabei, den gemeinsamen Speicher zwischen diesen Goroutinen zu realisieren. Die Ausführung von Goroutine erfordert sehr wenig Stapelspeicher (ca. 4 bis 5 KB) und wird natürlich entsprechend den entsprechenden Daten skaliert. Aus diesem Grund können Tausende gleichzeitiger Aufgaben gleichzeitig ausgeführt werden. Goroutine ist einfacher zu verwenden, effizienter und leichter als Thread.

goroutine ist ein Thread-Manager, der von der Laufzeit von Go verwaltet wird. Goroutine wird über das Schlüsselwort go implementiert, bei dem es sich eigentlich um eine gewöhnliche Funktion handelt. go关键字实现了,其实就是一个普通的函数。

go hello(a, b, c)

通过关键字go就启动了一个goroutine。我们来看一个例子

package main

import (
    "fmt"
    "runtime"
)

func say(s string) {
    for i := 0; i < 5; i++ {
        runtime.Gosched()
        fmt.Println(s)
    }
}

func main() {
    go say("world") //开一个新的Goroutines执行
    say("hello") //当前Goroutines执行
}

// 以上程序执行后将输出:
// hello
// world
// hello
// world
// hello
// world
// hello
// world
// hello

我们可以看到go关键字很方便的就实现了并发编程。 上面的多个goroutine运行在同一个进程里面,共享内存数据,不过设计上我们要遵循:不要通过共享来通信,而要通过通信来共享。

runtime.Gosched()表示让CPU把时间片让给别人,下次某个时候继续恢复执行该goroutine。

默认情况下,调度器仅使用单线程,也就是说只实现了并发。想要发挥多核处理器的并行,需要在我们的程序中显式调用 runtime.GOMAXPROCS(n) 告诉调度器同时使用多个线程。GOMAXPROCS 设置了同时运行逻辑代码的系统线程的最大数量,并返回之前的设置。如果n < 1,不会改变当前设置。以后Go的新版本中调度得到改进后,这将被移除。

channels

goroutine运行在相同的地址空间,因此访问共享内存必须做好同步。那么goroutine之间如何进行数据的通信呢,Go提供了一个很好的通信机制channel。channel可以与Unix shell 中的双向管道做类比:可以通过它发送或者接收值。这些值只能是特定的类型:channel类型。定义一个channel时,也需要定义发送到channel的值的类型。注意,必须使用make 创建channel:

ci := make(chan int)
cs := make(chan string)
cf := make(chan interface{})

channel通过操作符<-

ch <- v    // 发送v到channel ch.
v := <-ch  // 从ch中接收数据,并赋值给v

Starten Sie eine Goroutine über das Schlüsselwort go. Schauen wir uns ein Beispiel an

package main

import "fmt"

func sum(a []int, c chan int) {
    total := 0
    for _, v := range a {
        total += v
    }
    c <- total  // send total to c
}

func main() {
    a := []int{7, 2, 8, -9, 4, 0}

    c := make(chan int)
    go sum(a[:len(a)/2], c)
    go sum(a[len(a)/2:], c)
    x, y := <-c, <-c  // receive from c

    fmt.Println(x, y, x + y)
}

Wir können sehen, dass das Schlüsselwort go problemlos gleichzeitige Programmierung implementiert. Die oben genannten mehreren Goroutinen laufen im selben Prozess und teilen Speicherdaten. Wir müssen jedoch dem Design folgen: Kommunizieren Sie nicht durch Teilen, sondern teilen Sie durch Kommunikation.

runtime.Gosched() bedeutet, die CPU die Zeitscheibe an andere abgeben zu lassen und die Ausführung der Goroutine beim nächsten Mal irgendwann fortzusetzen. Standardmäßig verwendet der Scheduler nur einen einzelnen Thread, was bedeutet, dass nur Parallelität implementiert ist. Um die Parallelität von Mehrkernprozessoren zu nutzen, müssen wir runtime.GOMAXPROCS(n) in unserem Programm explizit aufrufen, um den Scheduler anzuweisen, mehrere Threads gleichzeitig zu verwenden. GOMAXPROCS legt die maximale Anzahl von Systemthreads fest, die gleichzeitig Logikcode ausführen können, und gibt die vorherige Einstellung zurück. Wenn n < 1, wird die aktuelle Einstellung nicht geändert. Dies wird entfernt, wenn die Planung in zukünftigen Versionen von Go verbessert wird.

channels

🎜🎜🎜goroutine läuft im gleichen Adressraum, daher muss der Zugriff auf den gemeinsam genutzten Speicher synchronisiert werden. Wie kann man also Daten zwischen Goroutinen kommunizieren? Go bietet einen guten Kommunikationsmechanismus. Ein Kanal kann mit einer bidirektionalen Pipe in einer Unix-Shell verglichen werden: Sie können über ihn Werte senden oder empfangen. Diese Werte können nur von einem bestimmten Typ sein: Kanaltyp. Wenn Sie einen Kanal definieren, müssen Sie auch die Art des an den Kanal gesendeten Werts definieren. Beachten Sie, dass der Kanal mit make erstellt werden muss: 🎜
ch := make(chan type, value)

value == 0 ! 无缓冲(阻塞)
value > 0 ! 缓冲(非阻塞,直到value 个元素)
🎜Der Kanal empfängt und sendet Daten über den Operator <- 🎜
package main

import "fmt"

func main() {
    c := make(chan int, 2)//修改2为1就报错,修改2为3可以正常运行
    c <- 1
    c <- 2
    fmt.Println(<-c)
    fmt.Println(<-c)
}
    //修改为1报如下的错误:
    //fatal error: all goroutines are asleep - deadlock!
🎜Wir wenden diese auf unser Beispiel an: 🎜
package main

import (
    "fmt"
)

func fibonacci(n int, c chan int) {
    x, y := 1, 1
    for i := 0; i < n; i++ {
        c <- x
        x, y = y, x + y
    }
    close(c)
}

func main() {
    c := make(chan int, 10)
    go fibonacci(cap(c), c)
    for i := range c {
        fmt.Println(i)
    }
}
🎜Standardmäßig der Kanal wird beim Empfangen und Senden von Daten blockiert, es sei denn, das andere Ende ist bereit, was die Synchronisierung von Goroutinen erleichtert, ohne dass explizite Sperren erforderlich sind. Das sogenannte Blockieren bedeutet, dass beim Lesen von (Wert := <-ch) dieser blockiert wird, bis Daten empfangen werden. Zweitens wird jeder Versand (ch<-5) blockiert, bis die Daten gelesen sind. Ungepufferte Kanäle sind ein großartiges Werkzeug zur Synchronisierung zwischen mehreren Goroutinen. 🎜🎜🎜🎜🎜🎜Gepufferte Kanäle🎜🎜🎜🎜Wir haben oben den standardmäßigen Nicht-Cache-Kanal eingeführt, aber Go ermöglicht Ihnen auch, die Puffergröße des Kanals anzugeben, d. h. wie viele Elemente der Kanal hat speichern kann. ch:= make(chan bool, 4), erstellt einen Bool-Kanal, der 4 Elemente speichern kann. In diesem Kanal können die ersten 4 Elemente ohne Blockierung geschrieben werden. Wenn das fünfte Element geschrieben wird, blockiert der Code, bis eine andere Goroutine einige Elemente aus dem Kanal liest, um Platz zu schaffen. 🎜
ch := make(chan type, value)

value == 0 ! 无缓冲(阻塞)
value > 0 ! 缓冲(非阻塞,直到value 个元素)

我们看一下下面这个例子,你可以在自己本机测试一下,修改相应的value值

package main

import "fmt"

func main() {
    c := make(chan int, 2)//修改2为1就报错,修改2为3可以正常运行
    c <- 1
    c <- 2
    fmt.Println(<-c)
    fmt.Println(<-c)
}
    //修改为1报如下的错误:
    //fatal error: all goroutines are asleep - deadlock!

Range和Close

上面这个例子中,我们需要读取两次c,这样不是很方便,Go考虑到了这一点,所以也可以通过range,像操作slice或者map一样操作缓存类型的channel,请看下面的例子

package main

import (
    "fmt"
)

func fibonacci(n int, c chan int) {
    x, y := 1, 1
    for i := 0; i < n; i++ {
        c <- x
        x, y = y, x + y
    }
    close(c)
}

func main() {
    c := make(chan int, 10)
    go fibonacci(cap(c), c)
    for i := range c {
        fmt.Println(i)
    }
}

for i := range c能够不断的读取channel里面的数据,直到该channel被显式的关闭。上面代码我们看到可以显式的关闭channel,生产者通过内置函数close关闭channel。关闭channel之后就无法再发送任何数据了,在消费方可以通过语法v, ok := <-ch测试channel是否被关闭。如果ok返回false,那么说明channel已经没有任何数据并且已经被关闭。

记住应该在生产者的地方关闭channel,而不是消费的地方去关闭它,这样容易引起panic

另外记住一点的就是channel不像文件之类的,不需要经常去关闭,只有当你确实没有任何发送数据了,或者你想显式的结束range循环之类的

Select

我们上面介绍的都是只有一个channel的情况,那么如果存在多个channel的时候,我们该如何操作呢,Go里面提供了一个关键字select,通过select可以监听channel上的数据流动。

select默认是阻塞的,只有当监听的channel中有发送或接收可以进行时才会运行,当多个channel都准备好的时候,select是随机的选择一个执行的。

package main

import "fmt"

func fibonacci(c, quit chan int) {
    x, y := 1, 1
    for {
        select {
        case c <- x:
            x, y = y, x + y
        case <-quit:
            fmt.Println("quit")
            return
        }
    }
}

func main() {
    c := make(chan int)
    quit := make(chan int)
    go func() {
        for i := 0; i < 10; i++ {
            fmt.Println(<-c)
        }
        quit <- 0
    }()
    fibonacci(c, quit)
}

select里面还有default语法,select其实就是类似switch的功能,default就是当监听的channel都没有准备好的时候,默认执行的(select不再阻塞等待channel)。

select {
case i := <-c:
    // use i
default:
    // 当c阻塞的时候执行这里
}

超时

有时候会出现goroutine阻塞的情况,那么我们如何避免整个程序进入阻塞的情况呢?我们可以利用select来设置超时,通过如下的方式实现:

func main() {
    c := make(chan int)
    o := make(chan bool)
    go func() {
        for {
            select {
                case v := <- c:
                    println(v)
                case <- time.After(5 * time.Second):
                    println("timeout")
                    o <- true
                    break
            }
        }
    }()
    <- o
}

runtime goroutine

runtime包中有几个处理goroutine的函数:

  • Goexit

    退出当前执行的goroutine,但是defer函数还会继续调用

  • Gosched

    让出当前goroutine的执行权限,调度器安排其他等待的任务运行,并在下次某个时候从该位置恢复执行。

  • NumCPU

    返回 CPU 核数量

  • NumGoroutine

    返回正在执行和排队的任务总数

  • GOMAXPROCS

    用来设置可以并行计算的CPU核数的最大值,并返回之前的值。

【相关推荐:Go视频教程编程教学

Das obige ist der detaillierte Inhalt vonIst Single-Threading eine Funktion der Go-Sprache?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn