Inhaltsverzeichnis
Lassen Sie uns über Gos Goroutine und Channel sprechen
Heim Backend-Entwicklung Golang Lassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen.

Lassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen.

Jul 07, 2021 pm 04:16 PM
go

Lassen Sie uns über Gos Goroutine und Channel sprechen

    • 1. Verwenden Sie den Kanal, um auf das Ende der Aufgabe zu warten Timer
      • 3. Zusammenfassung
      Empfohlene verwandte Artikel: „
    • Sprechen Sie über gleichzeitige Programmierung in Go (1)
      1. Verwenden Sie Kanäle, um auf das Ende der Aufgabe zu warten

    Der Anwendungsfall Im ersten Artikel befindet sich noch der in Abschnitt 2 geschriebene Code, hier ist jedoch nur ein Absatz erforderlich.

    package mainimport (
    	"fmt"
    	"time")func createWorker(id int) chan
    Nach dem Login kopieren

    Hier stellt Kaka den Original-Quellcode ein. Wenn Sie dem Rhythmus des Artikels folgen möchten, können Sie ihn in Ihren Editor einfügen und bedienen.

    Was ist dann das Problem mit diesem Code?

    Sie können sehen, dass am Ende des Kanals ein Schlaf verwendet wirdDemo-Funktion Dieses Ding kann im Programm nicht wahllos verwendet werden.

    Apropos, lass mich dir eine kleine Geschichte erzählen, die im Internet einen Code gesehen hat, der für Schlaf sorgt.

    Dann verstand ein unerfahrener Programmierer nicht, warum dieser Schlaf hinzugefügt wurde, und fragte dann den Projektmanager. Der Projektmanager sagte, dass er uns bitten würde, es zu optimieren, nachdem er festgestellt hatte, dass das Programm langsam sei Eine Optimierung würde die Schlafzeit verkürzen. Geben Sie dem Chef das Gefühl, dass wir etwas Gutes tun.

    Neulinge markieren den Code, wenn sie ihn nicht verstehen, und schreiben dann einen Kommentar: „Der Projektmanager hat hier nach langsamer Ausführung gefragt, und als der Chef nach Optimierung gefragt hat, war der Code deutlich schneller.“

    Leider wurde dieser Satz vom Chef gesehen. Der Chef kannte den Code nicht, aber er kannte trotzdem den Text! Also trat der Projektmanager zurück.

    Der Großteil von Sleep befindet sich also in einem Teststadium und wird definitiv nicht online erscheinen, na und? Es ist notwendig, diesen Schlaf im Code zu lösen.

    那么大家在回忆一下,在这里为什么要加sleep呢?

    发送到channel的数据都是在另一个goroutine中进行并发打印的,并发打印就会出现问题,因为根本不会知道什么时候才打印完毕。

    所以说这个sleep就会为了应对这个不知道什么时候打印完的问题,给个1毫秒让进行打印。

    这种做法是非常不好的,接下来看看使用一种新的方式来解决这个问题。

    以下代码是修改完的代码。

    package mainimport (
    	"fmt")type worker struct {
    	in   chan int
    	done chan bool}func createWorker(id int) worker {
    	w := worker{
    		in:   make(chan int),
    		done: make(chan bool),
    	}
    	go doWorker(id, w.in, w.done)
    	return w}func doWorker(id int, c chan int, done chan bool) {
    	for n := range c {
    		fmt.Printf("Worker %d receive %c\n", id, n)
    		done 
    Nach dem Login kopieren
    Nach dem Login kopieren

    将这些代码复制到你的本地,然后再来看一下都做了什么改动。

    • Zur Vereinfachung der Parameterübergabe haben wir zunächst einen Struktur-Worker eingerichtet
    • und die vorherige Worker-Methode in doWorker geändert
    • Zu diesem Zeitpunkt kann der Rückgabewert der createWorker-Methode nicht der vorherige Kanal sein, sondern der erstellter Strukturarbeiter
    • Dann erstellen Sie alle Kanäle in der Methode „createWorker“. Und verwenden Sie die Struktur, um Parameter an doWorker zu übergeben.
    • Was letztendlich zurückgegeben wird, ist die Struktur.
    • Der letzte Schritt besteht darin, den Wert von Workern[i] in den beiden Schleifen zu empfangen, die Daten in der Methode „channelDemo“ senden.

    Schauen Sie sich die Druckergebnisse an

    Lassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen.

    Sind Sie etwas verwirrt? Wenn es parallel ist, müssen Sie einfach die 10 Arbeiter öffnen Ordnung.

    Lassen Sie uns dieses Problem jetzt lösen. Ich möchte keine Aufgabe senden und warten, bis sie beendet ist.

    Das Beste ist, sie alle rauszuschicken und zu warten, bis sie alle fertig sind, bevor Sie gehen.

    代码实现如下

    package mainimport (
    	"fmt")type worker struct {
    	in   chan int
    	done chan bool}func createWorker(id int) worker {
    	w := worker{
    		in:   make(chan int),
    		done: make(chan bool),
    	}
    	go doWorker(id, w.in, w.done)
    	return w}func doWorker(id int, c chan int, done chan bool) {
    	for n := range c {
    		fmt.Printf("Worker %d receive %c\n", id, n)
    		done 
    Nach dem Login kopieren
    Nach dem Login kopieren

    在这里再进行打印看一下结果,你会发现代码是有问题的。

    Lassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen.

    为什么将小写的字母打印出来,而打印大写字母时发生了报错呢?

    这个就要追溯到代码中了,因为我们代码本身就写的有问题。

    还是回归到本文长谈的一个问题,那就是对于所有的channel有发送数据就必须有接收数据,如果没有接收数据就会报错。

    Können Sie im Code sehen, dass dieser Block nur Daten sendet, aber keine Daten empfängt?

    Lassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen.

    Das Problem besteht darin, dass nach dem Senden von Kleinbuchstaben an den Kanal die DoWorker-Methode eingegeben wird und dann ein True an „Done“ gesendet wird, aber die Methode zum Empfangen von „Done“ befindet sich hinten, d. h. an sagen: Wenn der zweite Großbuchstabe gesendet wird, wird eine Warteschleife gesendet.

    Die Lösung dieses Problems ist ebenfalls sehr einfach. Wir müssen nur gleichzeitig fertige Dateien senden.

    Lassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen.

    Das gedruckte Ergebnis ist auch korrekt.

    Der in diesem Artikel beschriebene Fall wird in normalen Projekten nicht vorkommen, sodass Sie sich darüber keine Sorgen machen müssen.

    Der angegebene Fall dient nur dazu, alle mit dem Kanalmechanismus vertrauter zu machen.

    Es gibt eine andere Lösung für diese Lösung, siehe Code.

    Lassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen.

    Stellen Sie den Code auf den vorherigen Stand wieder her und wiederholen Sie dann die Schleife, um unter jedem gesendeten Brief fertig zu werden.

    Für diese Multitasking-Wartemethode gibt es eine Bibliothek, die dies tun kann.

    Verwendung von sync.WaitGroup

    Ich werde die Verwendung von sync.WaitGroup nicht einzeln vorstellen. Schauen Sie sich einfach die Quellcode-Implementierung an.

    package mainimport (
    	"fmt"
    	"sync")type worker struct {
    	in chan int
    	wg *sync.WaitGroup}func createWorker(id int, wg *sync.WaitGroup) worker {
    	w := worker{
    		in: make(chan int),
    		wg: wg,
    	}
    	go doWorker(id, w.in, wg)
    	return w}func doWorker(id int, c chan int, wg *sync.WaitGroup) {
    	for n := range c {
    		fmt.Printf("Worker %d receive %c\n", id, n)
    		wg.Done()
    	}}func channelDemo() {
    	var wg sync.WaitGroup	var workers [10]worker	for i := 0; i 
    Nach dem Login kopieren

    这份源码也是非常简单的,具体修改得东西咔咔简单介绍一下。

    • 首先取消了channelDemo这个方法中关于done的channel。
    • 使用了sync.WaitGroup,并且给createWorker方法传递sync.WaitGroup
    • createWorker方法使用了 worker的结构体。
    • 所以要先修改worker结构体,将之前的done改为wg *sync.WaitGroup即可
    • 这样就可以直接用结构体的数据。
    • 接着在doWorker方法中把最后一个参数done改为wg *sync.WaitGroup
    • 将方法中的done改为wg.Done()
    • 最后一步就是回到函数channelDemo中把任务数添加进去,然后在代码最后添加一个等待即可。

    关于这块的内容先知道这么用即可,咔咔后期会慢慢的补充并且深入。

    抽象代码

    这块的代码看起来不是那么的完美的,接下来抽象一下。

    Lassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen.

    这块代码有没有发现有点蹩脚,接下来我们使用函数式编程进行简单的处理。

    package mainimport (
    	"fmt"
    	"sync")type worker struct {
    	in   chan int
    	done func()}func createWorker(id int, wg *sync.WaitGroup) worker {
    	w := worker{
    		in: make(chan int),
    		done: func() {
    			wg.Done()
    		},
    	}
    	go doWorker(id, w)
    	return w}func doWorker(id int, w worker) {
    	for n := range w.in {
    		fmt.Printf("Worker %d receive %c\n", id, n)
    		w.done()
    	}}func channelDemo() {
    	var wg sync.WaitGroup	var workers [10]worker	for i := 0; i 
    Nach dem Login kopieren

    这块代码看不明白就先放着,写的时间长了,你就会明白其中的含义了,学习东西不要钻牛角尖。

    二、使用select进行调度

    开头先给一个问题,假设现在有俩个channel,谁来的快先收谁应该怎么做?

    package mainimport (
    	"fmt"
    	"math/rand"
    	"time")func generator() chan int {
    	out := make(chan int)
    	go func() {
    		i := 0
    		for {
    			// 随机睡眠1500毫秒以内
    			time.Sleep(
    				time.Duration(rand.Intn(1500)) *
    					time.Millisecond)
    			// 往out这个channel发送i值
    			out 
    Nach dem Login kopieren

    以上就是代码实现,代码注释也写的非常的清晰明了,就不过多的做解释了。

    主要用法还是对channel的使用,在带上了一个新的概念select,可以在多个通道,那个通道先发送数据,就先执行谁,并且这个select也是可以并行执行channel管道。

    在上文写的createWorkerworker俩个方法还记得吧!接下来就不在select里边直接打印了。

    就使用之前写的俩个方法融合在一起,咔咔已将将源码写好了,接下来看一下实现。

    package mainimport (
    	"fmt"
    	"math/rand"
    	"time")func worker(id int, c chan int) {
    	for n := range c {
    		fmt.Printf("Worker %d receive %d\n", id, n)
    	}}func createWorker(id int) chan
    Nach dem Login kopieren
    Nach dem Login kopieren

    运行代码

    Lassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen.

    看到Lassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen.得知也是没有问题的。

    这段代码虽然运行没有任何问题,但是这样有什么缺点呢?

    Lassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen.

    可以看下这段代码n := 这里先收了一个值,然后在下边代码<code style="box-sizing: border-box; font-family: " source code pro sans mono menlo monaco consolas inconsolata courier monospace sc yahei sans-serif font-size: background-color: rgb border-radius: padding: line-height: color:>w 又会阻塞住,这个是不好的。

    那么希望是怎么执行的呢?

    Lassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen.

    这种模式是在select中既可以收数据,也可以发数据,目前这个程序是编译不过的,请看修改后的源码。

    package mainimport (
    	"fmt"
    	"math/rand"
    	"time")func worker(id int, c chan int) {
    	for n := range c {
    		fmt.Printf("Worker %d receive %d\n", id, n)
    	}}func createWorker(id int) chan
    Nach dem Login kopieren
    Nach dem Login kopieren

    这个模式还是有缺点的,因为n收c1和c2的速度跟消耗的速度是不一样的。

    假设c1的生成速度特别快,一下子生成了1,2,3。那么最后输出的数据有可能就只有3,而1和2就无法输出了。

    这个场景也是非常好模拟的,只需要在打印的位置加上一点延迟时间即可。

    Lassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen.

    此时你会看到Lassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen.为0、7、12、20…中间很多的数字都没来得急打印。

    因此我们就需要把收到的n存下来进行排队输出。

    package mainimport (
    	"fmt"
    	"math/rand"
    	"time")func worker(id int, c chan int) {
    	for n := range c {
    		// 手动让消耗速度变慢
    		time.Sleep(5 * time.Second)
    		fmt.Printf("Worker %d receive %d\n", id, n)
    	}}func createWorker(id int) chan 0 {
    			activeWorker = worker			// 取出索引为0的值
    			activeValue = values[0]
    		}
    		/**
    		select 方式进行调度
    		        使用场景:比如有多个通道,但我打算是哪一个通道先给我数据,我就先执行谁
    		        这个select 可以是并行执行 channel管道
    		*/
    		select {
    		case n := 
    Nach dem Login kopieren

    以上就是实现代码

    此时在来看Lassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen.。

    Lassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen.

    Lassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen.没有漏掉数据,并且也是无序的,这样就非常好了。

    计时器的使用

    上面的这个程序是退出不了的,我们想让它10s后就直接退出怎么做呢?

    那就需要使用计时器来进行操作了。

    package mainimport (
    	"fmt"
    	"math/rand"
    	"time")func worker(id int, c chan int) {
    	for n := range c {
    		// 手动让消耗速度变慢
    		time.Sleep(time.Second)
    		fmt.Printf("Worker %d receive %d\n", id, n)
    	}}func createWorker(id int) chan 0 {
    			activeWorker = worker			// 取出索引为0的值
    			activeValue = values[0]
    		}
    		/**
    		select 方式进行调度
    		        使用场景:比如有多个通道,但我打算是哪一个通道先给我数据,我就先执行谁
    		        这个select 可以是并行执行 channel管道
    		*/
    		select {
    		case n := 
    Nach dem Login kopieren
    Nach dem Login kopieren

    这里就是源码的实现,可以看到直接在select中是可以收到tm的值的,也就说如果到了10s,就会执行打印bye的操作。

    Jetzt gibt es also eine weitere Anforderung, das heißt, wenn die Daten nicht innerhalb von 800 Millisekunden empfangen werden, können andere Dinge getan werden.

    Anhand der Idee, aus einem Beispiel Schlussfolgerungen zu ziehen, können Sie darüber nachdenken, wie das geht.

    Lassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen.

    Es ist eigentlich ganz einfach, Sie müssen nur einen Timer im Gehäuse einstellen.

    Nachdem ich dies erwähnt habe, möchte ich für Sie eine weitere Verwendung hinzufügenLassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen. := time.Tick(time.Second)

    , die auch in Fällen verwendet wird.

    Lassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen.

    这样就可以每秒来显示一下values队列有多少数据。

    这块的内容就结束了,最终给大家发一下源码,感兴趣的可以在自己的编辑器上试试看。

    package mainimport (
    	"fmt"
    	"math/rand"
    	"time")func worker(id int, c chan int) {
    	for n := range c {
    		// 手动让消耗速度变慢
    		time.Sleep(time.Second)
    		fmt.Printf("Worker %d receive %d\n", id, n)
    	}}func createWorker(id int) chan 0 {
    			activeWorker = worker			// 取出索引为0的值
    			activeValue = values[0]
    		}
    		/**
    		select 方式进行调度
    		        使用场景:比如有多个通道,但我打算是哪一个通道先给我数据,我就先执行谁
    		        这个select 可以是并行执行 channel管道
    		*/
    		select {
    		case n := 
    Nach dem Login kopieren
    Nach dem Login kopieren

    三、总结

    本文主要就是对于goroutine和channel的大量练习。

    文中的案例,有可能会一时半会理解不了,是没有关系的,不用钻牛角尖的。

    Wenn man längere Zeit im Meer schwimmt, werden einem einige Dinge ganz natürlich klar.

    Der nächste Artikel soll Ihnen ein praktisches, paralleles Crawler-Projekt vorstellen.

    Beharrlichkeit beim Lernen, Beharrlichkeit beim Schreiben und Beharrlichkeit beim Teilen sind die Überzeugungen, an denen Kaka seit seinen Anfängen festgehalten hat. Ich hoffe, dass Kakas Artikel im riesigen Internet Ihnen ein wenig helfen können. Ich bin Kaka, bis zum nächsten Mal.

    Das obige ist der detaillierte Inhalt vonLassen Sie uns über die gleichzeitige Programmierung in Go (2) sprechen.. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

    Erklärung dieser Website
    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

    Heiße KI -Werkzeuge

    Undresser.AI Undress

    Undresser.AI Undress

    KI-gestützte App zum Erstellen realistischer Aktfotos

    AI Clothes Remover

    AI Clothes Remover

    Online-KI-Tool zum Entfernen von Kleidung aus Fotos.

    Undress AI Tool

    Undress AI Tool

    Ausziehbilder kostenlos

    Clothoff.io

    Clothoff.io

    KI-Kleiderentferner

    Video Face Swap

    Video Face Swap

    Tauschen Sie Gesichter in jedem Video mühelos mit unserem völlig kostenlosen KI-Gesichtstausch-Tool aus!

    Heiße Werkzeuge

    Notepad++7.3.1

    Notepad++7.3.1

    Einfach zu bedienender und kostenloser Code-Editor

    SublimeText3 chinesische Version

    SublimeText3 chinesische Version

    Chinesische Version, sehr einfach zu bedienen

    Senden Sie Studio 13.0.1

    Senden Sie Studio 13.0.1

    Leistungsstarke integrierte PHP-Entwicklungsumgebung

    Dreamweaver CS6

    Dreamweaver CS6

    Visuelle Webentwicklungstools

    SublimeText3 Mac-Version

    SublimeText3 Mac-Version

    Codebearbeitungssoftware auf Gottesniveau (SublimeText3)

    Wie sende ich Go WebSocket-Nachrichten? Wie sende ich Go WebSocket-Nachrichten? Jun 03, 2024 pm 04:53 PM

    In Go können WebSocket-Nachrichten mit dem Paket gorilla/websocket gesendet werden. Konkrete Schritte: Stellen Sie eine WebSocket-Verbindung her. Senden Sie eine Textnachricht: Rufen Sie WriteMessage(websocket.TextMessage,[]byte("message")) auf. Senden Sie eine binäre Nachricht: Rufen Sie WriteMessage(websocket.BinaryMessage,[]byte{1,2,3}) auf.

    Vertiefendes Verständnis des Golang-Funktionslebenszyklus und des Variablenumfangs Vertiefendes Verständnis des Golang-Funktionslebenszyklus und des Variablenumfangs Apr 19, 2024 am 11:42 AM

    In Go umfasst der Funktionslebenszyklus Definition, Laden, Verknüpfen, Initialisieren, Aufrufen und Zurückgeben; der Variablenbereich ist in Funktionsebene und Blockebene unterteilt. Variablen innerhalb einer Funktion sind intern sichtbar, während Variablen innerhalb eines Blocks nur innerhalb des Blocks sichtbar sind .

    Wie kann ich Zeitstempel mithilfe regulärer Ausdrücke in Go abgleichen? Wie kann ich Zeitstempel mithilfe regulärer Ausdrücke in Go abgleichen? Jun 02, 2024 am 09:00 AM

    In Go können Sie reguläre Ausdrücke verwenden, um Zeitstempel abzugleichen: Kompilieren Sie eine Zeichenfolge mit regulären Ausdrücken, z. B. die, die zum Abgleich von ISO8601-Zeitstempeln verwendet wird: ^\d{4}-\d{2}-\d{2}T \d{ 2}:\d{2}:\d{2}(\.\d+)?(Z|[+-][0-9]{2}:[0-9]{2})$ . Verwenden Sie die Funktion regexp.MatchString, um zu überprüfen, ob eine Zeichenfolge mit einem regulären Ausdruck übereinstimmt.

    Der Unterschied zwischen Golang und Go-Sprache Der Unterschied zwischen Golang und Go-Sprache May 31, 2024 pm 08:10 PM

    Go und die Go-Sprache sind unterschiedliche Einheiten mit unterschiedlichen Eigenschaften. Go (auch bekannt als Golang) ist bekannt für seine Parallelität, schnelle Kompilierungsgeschwindigkeit, Speicherverwaltung und plattformübergreifende Vorteile. Zu den Nachteilen der Go-Sprache gehören ein weniger umfangreiches Ökosystem als andere Sprachen, eine strengere Syntax und das Fehlen dynamischer Typisierung.

    Wie vermeidet man Speicherlecks bei der technischen Leistungsoptimierung von Golang? Wie vermeidet man Speicherlecks bei der technischen Leistungsoptimierung von Golang? Jun 04, 2024 pm 12:27 PM

    Speicherlecks können dazu führen, dass der Speicher des Go-Programms kontinuierlich zunimmt, indem: Ressourcen geschlossen werden, die nicht mehr verwendet werden, wie z. B. Dateien, Netzwerkverbindungen und Datenbankverbindungen. Verwenden Sie schwache Referenzen, um Speicherlecks zu verhindern, und zielen Sie auf Objekte für die Garbage Collection ab, wenn sie nicht mehr stark referenziert sind. Bei Verwendung von Go-Coroutine wird der Speicher des Coroutine-Stapels beim Beenden automatisch freigegeben, um Speicherverluste zu vermeiden.

    Wie kann ich die Golang-Funktionsdokumentation in der IDE anzeigen? Wie kann ich die Golang-Funktionsdokumentation in der IDE anzeigen? Apr 18, 2024 pm 03:06 PM

    Go-Funktionsdokumentation mit der IDE anzeigen: Bewegen Sie den Cursor über den Funktionsnamen. Drücken Sie den Hotkey (GoLand: Strg+Q; VSCode: Nach der Installation von GoExtensionPack F1 und wählen Sie „Go:ShowDocumentation“).

    Eine Anleitung zum Unit-Testen gleichzeitiger Go-Funktionen Eine Anleitung zum Unit-Testen gleichzeitiger Go-Funktionen May 03, 2024 am 10:54 AM

    Das Testen gleichzeitiger Funktionen in Einheiten ist von entscheidender Bedeutung, da dies dazu beiträgt, ihr korrektes Verhalten in einer gleichzeitigen Umgebung sicherzustellen. Beim Testen gleichzeitiger Funktionen müssen grundlegende Prinzipien wie gegenseitiger Ausschluss, Synchronisation und Isolation berücksichtigt werden. Gleichzeitige Funktionen können Unit-Tests unterzogen werden, indem Rennbedingungen simuliert, getestet und Ergebnisse überprüft werden.

    Was ist zu beachten, wenn Golang-Funktionen Kartenparameter empfangen? Was ist zu beachten, wenn Golang-Funktionen Kartenparameter empfangen? Jun 04, 2024 am 10:31 AM

    Beim Übergeben einer Karte an eine Funktion in Go wird standardmäßig eine Kopie erstellt und Änderungen an der Kopie haben keinen Einfluss auf die Originalkarte. Wenn Sie die Originalkarte ändern müssen, können Sie sie über einen Zeiger übergeben. Leere Karten müssen mit Vorsicht behandelt werden, da es sich technisch gesehen um Nullzeiger handelt und die Übergabe einer leeren Karte an eine Funktion, die eine nicht leere Karte erwartet, einen Fehler verursacht.

    See all articles