chan的危险
channel是Go语言在语言级别提供的goroutine间的通信方式。可以通过channel实现生产者与消费者。虽然多个goroutine可以同时从一个channel中取元素,但一个元素只会被一个goroutine取得,所以不要使用goroutine实现广播功能,更不要通过channel控制所有goroutine的结束。
package ct
import (
"fmt"
"testing"
)
func Run(channel chan int) {
defer func() {
fmt.Printf("end ....\n")
}()
lable:
v, _ := <-channel
if v == 0 {
return
} else {
goto lable
}
}
func TestChan3(t *testing.T) {
var channel chan int = make(chan int, 10)
for index := 0; index < 15; index++ {
go Run(channel)
}
for index := 0; index < 10; index++ {
channel <- 0
}
}
这是一个简单的通过往channel写入信号量0控制goroutine结束的例子。很容易从代码中看出弊端。当for次数大于运行Run方法的goroutine的数量时,for所在的goroutine将会一直被阻塞;而当for次数少于运行Run方法的goroutine的数量时,会导致剩余goroutine得不到退出,一直在阻塞等待。
chan有界,越界必阻
channel是有大小的,在make时指定的大小就是channel所能存放元素的数量。当channel中存放的元素等于该大小,再往channel中放入元素时,当前goroutine就会被阻塞,直到有其它goroutine从channel里面取走元素,空出位置。
package ct
import (
"fmt"
"testing"
)
func goSend(msgChan chan int) {
for index := 0; index < 100; index++ {
fmt.Printf("send: %v\n", index)
msgChan <- index
}
}
func goRevice(msgChan chan int) {
v, _ := <-msgChan
fmt.Printf("revice: %v\n", v)
}
func TestChan(t *testing.T) {
msgChan := make(chan int, 1)
go goRevice(msgChan)
goSend(msgChan)
}
这个代码的输出结果为:
send: 0
send: 1
revice: 0
send: 2
Tests timed out after 60000 ms
例子中,channel的大小设置为1。goRevice在另一个goroutine中运行,当channel中没有元素时会阻塞。goSend第一次打印输出send 0,接着往队列中写入元素0,接着第二次打印输出send 1,而此时channel中的元素还没有被取出,所以goSend会被阻塞;接着goRevice从channel中拿到一个元素,打印输出revice: 0;接着goSend成功放入元素1,此时goRevice已经结束,而channel中的元素1已经没有人取了,所以goSend在打印完send: 2之后,陷入死等待状态,直到Tests timed out。
循环中使用defer
这是我在做消息消费的业务的时候遇到的。每次从消费队列中拉取10条消息,通过遍历逐条消费,当消费成功时,需要将消费删除。通过defer实现无论消费成功或失败都将消息移除,正常情况下没有什么问题。当程序中抛出panic时,for循环停止,defer也会执行一次。
import (
"errors"
"fmt"
"testing"
"time"
)
func CreateError() {
panic(errors.New("error"))
}
func TestDefer(t *testing.T) {
var msgs []string = make([]string, 3)
msgs[0] = "wu"
msgs[1] = "jiu"
msgs[2] = "ye"
for _, message := range msgs {
defer func(msg string) {
fmt.Printf("=====> do defer, by %s ...\n", msg)
if err := recover(); err != nil {
fmt.Println(err)
}
}(message)
CreateError()
}
}
不建议这样使用,因为defer只有在当前方法结束前(包括异常结束)才会去执行,也就是说在for循环中定义的defer本意是想实现try、finally的,但实际上却相当于for循环执行完业务逻辑再执行一遍for进行删除。
一个方法中可以定义多个defer,defer最终会按照定义的顺序倒叙执行,定义在异常之后的defer不会被执行。比如
func TestDefer(t *testing.T) {
var msgs []string = make([]string, 3)
msgs[0] = "wu"
msgs[1] = "jiu"
msgs[2] = "ye"
defer func() {
fmt.Printf("end .....\n")
}()
defer func() {
fmt.Printf("end2 ....\n")
}()
for _, message := range msgs {
if message == "ye" {
CreateError()
}
}
defer func() {
fmt.Printf("end3 ....\n")
}()
}
上面程序输出结果如下:
end2 ....
end ....