Maison > Article > développement back-end > Introduction détaillée aux chaînes en langage Go
Cet article concerne la chaîne dans la langue go La raison pour laquelle je l'écris est parce que je pense que cette chaîne est très importante en même temps. Le canal est également un point de support important pour la concurrence go, car go utilise la mémoire partagée pour transmettre des messages au lieu d'utiliser la mémoire partagée pour communiquer.
La programmation simultanée est très bonne, mais la concurrence est très compliquée. La difficulté réside dans la coordination. La façon de gérer la communication entre les différents programmes est très importante. Avant d'écrire sur l'utilisation et les caractéristiques des canaux, nous devons examiner la communication inter-processus dans le système d'exploitation.
Communication inter-processus
Il existe deux modèles généraux de communication en ingénierie : les données partagées et les messages. Comme son nom l'indique, la communication de processus fait référence à l'échange d'informations entre processus, car l'exclusion mutuelle et la synchronisation des processus nécessitent l'échange d'informations entre processus. Quiconque a étudié les systèmes d'exploitation sait que la communication de processus peut être grossièrement divisée en processus de bas niveau. communication et communication de processus de haut niveau. Maintenant, il s'agit essentiellement de communications de processus avancées. Parmi eux, les mécanismes de communication avancés peuvent être divisés en : système de transmission de messages, système de mémoire partagée, système de communication par pipeline et système client-serveur.
1. Système de transmission de messages
Il ne repose sur aucune zone de stockage partagée ni sur une certaine structure de données. Il utilise des messages formatés comme unités pour utiliser le système à fournir. Les primitives de communication sont utilisées pour compléter l'échange de données, ce qui semble très inefficace.
2. Système de mémoire partagée
Les processus communicants partagent des zones de stockage ou des structures de données. Cette méthode est relativement courante, comme un certain fichier en tant que support.
3. Système client-serveur
Plusieurs autres mécanismes de communication sont essentiellement sur le même ordinateur (on peut dire qu'il s'agit du même environnement, bien sûr, dans certains). Dans certains cas, la communication entre ordinateurs peut être réalisée ci-dessous. Le système client-serveur est différent. Je crois comprendre qu'il peut être considéré comme une requête IP et qu'un client demande à se connecter à un serveur.
Cette méthode est désormais plus populaire sur Internet. La planification à distance est désormais plus couramment utilisée, comme RPC (ce qui semble très haut de gamme, mais elle est présente dans le système d'exploitation depuis longtemps) et les sockets. . , socket, ceci est assez couramment utilisé et est étroitement lié à notre programmation, car vous constaterez que de nombreux services doivent utiliser des appels RPC.
4. Système de communication par canal
Enfin, parlons en détail du mécanisme de communication par canal Au niveau du système d'exploitation, les canaux sont utilisés pour relier un processus de lecture. et un processus d'écriture de fichiers qui permettent la communication entre eux. C'est ce qu'on appelle un fichier pipe sur le système.
Les mécanismes implémentés sont tels que : Les tuyaux fournissent les deux fonctions suivantes :
1. Exclusivité mutuelle. Lorsqu'un processus effectue une opération de lecture ou d'écriture sur un fichier canal, les autres processus doivent attendre ou. bloquer ou dormir.
2. Synchronicité. Lorsque le processus d'écriture (entrée) écrit le fichier pipe, il attendra, se bloquera ou se mettra en veille jusqu'à ce que le processus de lecture (sortie) le réveille après avoir pris les données. go Lors de la lecture d'un fichier pipe vide, il attendra, se bloquera ou se mettra également en veille jusqu'à ce que le processus d'écriture le réveille après avoir écrit dans le tube.
L'utilisation du canal
doit correspondre au quatrième type de canal en go Le canal du langage go est une méthode de communication entre goroutines prévue au niveau du langage. . Cela n'a aucun sens de parler uniquement de canal, car il est efficace avec goroutine. Voyons d'abord comment les langages généraux résolvent la mémoire partagée entre les programmes.
Ce qui suit est un programme que nous connaissons :
package main import "fmt" var counts int = 0 func Count() { counts++ fmt.Println(counts) } func main() { for i := 0; i < 3; i++ { go Count() } }
Quiconque a appris go devrait en connaître la raison, car : le programme Go initialise la méthode et le package main(), puis exécute la fonction main(), mais lorsque la fonction main() revient, le programme se ferme. Le programme principal n'attend pas les autres goroutines, ce qui n'entraîne aucune sortie.
Regardons comment les langages conventionnels résolvent ce problème de concurrence :
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 } } }
La solution est un peu drôle, ajouter un tas de verrous, car son exécution est comme ceci : Code For la variable de verrouillage, chaque opération sur les comptes doit être verrouillée en premier. Une fois l'opération terminée, le verrou doit être déverrouillé. Dans la fonction principale, une boucle for est utilisée pour vérifier en permanence la valeur du compteur, et bien sûr, le verrou doit être déverrouillé. également être verrouillé.
Lorsque sa valeur atteint 3, cela signifie que toutes les goroutines ont été exécutées. A ce moment, la fonction principale revient et le programme se termine. Cette méthode est le moyen privilégié pour résoudre la concurrence dans les langages populaires. Vous pouvez voir que beaucoup de choses ont été écrites pour résoudre la concurrence. Si un projet commence à prendre forme, je ne sais pas combien de verrous doivent être ajoutés.
Voyons comment le canal résout ce problème :
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 } } }
Afin de voir clairement les étapes d'exécution de la goroutine et les caractéristiques du canal, j'ai spécialement imprimé chaque étape. Voici l'exécution des étudiants intéressés. peuvent l'essayer par eux-mêmes. L'ordre d'impression peut être différent :
Analysons ce processus et voyons le rôle du canal dans celui-ci. Le programme principal démarre :
Imprimez "0 ForStart 0 ForEnd", indiquant que i = 0 cette boucle a commencé l'exécution et la première goroutine a démarré
打印 "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语言教程
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!