这是Golang的一系列并发设计模式中的第一个博客。我将添加更多具有许多复杂设计模式的博客。如果您想通知,请订阅我的Newsletter
从设计模式开始之前,这里有一些先决条件。
- 如果您对Golang的语法感到满意,那将是最好的。
让我们在讨论设计模式之前先仔细考虑一些概念。 (如果您知道所有这些都可以随意跳到设计模式)
goroutines
简单地说,goroutine是与其他代码同时运行的代码的一部分。我们不会涉及太多理论(我们可以将其留给其他博客)。
要声明goroutine,您只需要在调用功能时使用GO前缀。
这是一个简单的示例:
func main(){
go func(){
for i:= 0 ;i<5;i++{
fmt.Print(i + " ")
}
}()
go func(){
for i:= 5 ;i<10;i++{
fmt.Println(i + " ")
}
}()
}
此代码段的输出将是这样的:
0 1 5 2 6 7 3 8 9 4
# It does not have to be exactly same.
频道
通道是我们并发设计模式中最重要的构建块之一。简单地说
通道只是可用于将数据从一个goroutine发送到另一个的排队。
以下是有关渠道的一些重要事实:
- 频道可以是缓冲(可以容纳一个或多个值)或 (不能容纳任何值)
-
频道正在阻止。
- 如果您将对象推向填充的通道,则该程序将在从频道弹出另一个对象
之前继续进行。
- 如果您是从空通道中弹出元素,则该程序将在将对象推向频道之前不会进一步进行。
// This is a read channel. You can only read values
var readChannel <-chan interface{}
// This is a write channel. You can only write values to this channel
var writeChannel chan<- interface{}
//You can read as well as write to this channel
var channel chan interface{}
channel1 <- val // we are pushing a value to a channel
val <- channel2 // we are reading from a channel
现在让我们跳到有趣的部分
什么是Goroutine泄漏?
我们使用goroutine执行一些操作,一段时间后,将结果发送给我们并终止。但是,如果不终止会发生什么?好吧,这就是我们所说的goroutine泄漏。
即使goroutines轻巧,如果我们调用很多goroutines,我们也会浪费很多资源。
在讨论之前,我们将如何防止Goroutine泄漏,让我们检查一个例子:
func test() <-chan int {
outStream := make(chan int)
go func(outStream chan<- int) {
for i := 0; i < 10; i-- {
outStream <- i
}
}(outStream)
return outStream
}
func main() {
inStream := test()
for i := 0; i < 10; i++ {
val := <-inStream
fmt.Println(val)
time.Sleep(1 * time.Second)
}
}
在这里,我们有一个 test 函数返回读取通道(我们只能从此通道读取值)。然后,它调用goroutine并以写入通道的方式传递通道(我们只能写入此通道)。我们正在运行一个用于循环的循环,并将值写入频道。但这是有趣的部分, for循环是无限的!我们已经这样做是为了模仿我们的goroutine不会终止并保持价值的情况。
运行代码时,我们将获得以下输出:
我们在10次迭代后停止了循环,但可以永远持续。
我们在10次迭代后停止了循环,但它可以永远持续。
-
要阻止Goroutine永远执行,父母需要发送一些信号。 是父母,我们的意思是调用goroutine的函数。
-
当孩子收到信号时,它会停止操作。由于Goroutines只能通过频道进行通信,因此父母会将频道发送到其子女Goroutine。
-
父母要终止孩子后,它就可以关闭频道。孩子收到此信号,还终止了其动作。
让我们看一下代码实现以更好地理解它
func test(terminate <-chan bool) <-chan int {
outStream := make(chan int)
go func(terminate <-chan bool, outStream chan<- int) {
defer close(outStream) // Channel is closed when the function is finishes executing
for i := 0; i < 10; i-- {
select {
case <-terminate:
fmt.Println("Child Terminating")
return
case outStream <- i:
}
}
}(terminate, outStream)
return outStream
}
func main() {
terminate := make(chan bool)
inStream := test(terminate)
for i := 0; i < 10; i++ {
val, ok := <-inStream
// If !ok, then it means channel is closed and we won't receive new value
if !ok {
fmt.Println("Channel closed")
break
}
fmt.Println(val)
time.Sleep(1 * time.Second)
if i == 5 {
close(terminate)
}
}
}
好吧,让我们讨论此代码的工作原理
-
我们制作了一个名为'terminate'的新频道,并将其作为只读渠道传递给孩子。
-
在无限的循环中,我们使用选择语句。 (Select语句就像开关语句一样,它在相应的
时执行代码块
-
现在,当频道被父母关闭时,将闭合信号发送给孩子。然后,它执行其相应的代码块,即返回语句。打破了无限环路。
-
现在,Goroutine终止了,关闭了“ Outstream”频道,从而终止了孩子。
现在,如果我们执行代码,它看起来像这样:
感谢您的阅读直到最后。如果您喜欢我的博客,请订阅我的newsletter以获取更多很棒的内容。
封面图片来源:https://www.storj.io/blog/finding-goroutine-leaks-in-tests