Goroutines and Channels and Select

本文详细介绍了Go语言中的并发编程概念,包括多线程的创建、通道的使用、缓冲通道、通道同步、选择器、超时处理、非阻塞通道操作以及通道关闭。通过实例展示了如何利用Go的并发特性进行高效且安全的多任务处理,强调了通道在并发控制中的重要作用。

多线程

首先创建多线程方法:

func f(from string) {
	for i := 0; i < 3; i++ {
		fmt.Println(from, ":", i)
	}
}
f("direct")
go f("goroutine")
go func(msg string) {
	fmt.Println(msg)
}("going")
time.Sleep(time.Second)
fmt.Println("done")

direct : 0
direct : 1
direct : 2
going
goroutine : 0
goroutine : 1
goroutine : 2
done

通道

通道是连接并发线程的管道。一个线程将值发送到管道,另一个线程从管道中接收该值。

messages := make(chan string)
go func() {
	time.Sleep(time.Second)
	messages <- "ping"
}()
msg := <-messages
fmt.Println(msg)

ping

msg一直等待messages通道返回数据,直到messages通道传入数据。

注意,该通道是无缓冲的,这意味着,发送的值,只有当相应的接收者<- chan准备接收时,通道才会允许发送chan <-

缓冲通道

messages := make(chan string, 2)
messages <- "buffered"
messages <- "channel"
fmt.Println(<-messages)
fmt.Println(<-messages)

buffered
channel

缓冲通道接收指定数量的值,即使不存在相应的接收者接收这些值。

通道同步执行

func worker(done chan bool) {
	fmt.Print("working...")
	time.Sleep(time.Second)
	fmt.Println("done")
	done <- true
}
done := make(chan bool, 1)
go worker(done)
<-done

working…done

通道可用于跨线程同步执行。上面的代码中,通道阻塞了主线程的运行,并在等待多线程运行完毕时,继续运行主线程。

通道方向

当通道作为参数使用时,可以指定通道参数仅是发送者还是接收者。

func ping(pings chan<- string, msg string) {
	pings <- msg
}
func pong(pings <-chan string, pongs chan<- string) {
	msg := <-pings
	pongs <- msg
}
pings := make(chan string, 1)
pongs := make(chan string, 1)
ping(pings, "passed message")
pong(pings, pongs)
fmt.Println(<-pongs)

passed message

以上代码中,pings通道用于接收数据,pongs通道用于发送数据。ping方法将数据传给pings;pong方法将数据从pings传到pongs(这里pings作为发送者,pongs作为接收者)。最后pongs将数据传出。

选择器

选择器用于等待多个通道的活动。

c1 := make(chan string)
c2 := make(chan string)
go func() {
	time.Sleep(1 * time.Second)
	c1 <- "one"
}()
go func() {
	time.Sleep(2 * time.Second)
	c2 <- "two"
}()
for i := 0; i < 2; i++ {
	select {
	case msg1 := <-c1:
		fmt.Println("received", msg1)
	case msg2 := <-c2:
		fmt.Println("received", msg2)
	}
}

received one
received two

select代码块每次等待且仅等待一个channel返回数据。第一个channel延迟1秒后传入数据,select的case立即做出响应并执行,然后select结束执行;下一个select开始等待执行,直到第二个channel延迟2秒后传入数据,select的case立即作出响应并执行,select结束执行。

超时

select和channel配合使用时,可能会有等待过程。等待时间过长时,需要做超时处理。

c := make(chan string, 1)
go func() {
	time.Sleep(2 * time.Second)
	c <- "result 1"
}()
select {
case res := <-c:
	fmt.Println(res)
case <-time.After(1 * time.Second):
	fmt.Println("timeout 1")
}

timeout 1

以上代码中,select分别等待两个channel;其中第二个case先执行,select不再等待第一个case并结束。

c := make(chan string, 1)
go func() {
	time.Sleep(2 * time.Second)
	c <- "result 2"
}()
select {
case res := <-c:
	fmt.Println(res)
case <-time.After(3 * time.Second):
	fmt.Println("timeout 2")
}

result 2

以上代码中,select分别等待两个channel;其中第一个case先执行,select不再等待第二个case并结束。

注意,time.After的返回数据是<-chan Time类型。

非阻塞通道操作

通道的基本发送和接收操作是阻塞式的,然而有时候并不希望代码运行时被阻塞,此时可以使用select的default子句实现非阻塞发送接收操作。

messages := make(chan string)
select {
case msg := <-messages:
	fmt.Println("reveived message", msg)
default:
	fmt.Println("no message received")
}

no message received

因为通道没有数据,也无法返回数据,所以select不执行case代码块,而执行default代码块。

messages := make(chan string)
signals := make(chan bool)
msg := "hi"
select {
case messages <- msg:
	fmt.Println("sent message", msg)
default:
	fmt.Println("no message sent")
}
select {
case msg := <-messages:
	fmt.Println("received message", msg)
case sig := <-signals:
	fmt.Println("received signal", sig)
default:
	fmt.Println("no activity")
}

no message sent
no activity

以上代码中,通道无法接收数据,这是因为通道既没有缓冲区,也没有接收者。

messages := make(chan string, 1)
signals := make(chan bool)
msg := "hi"
select {
case messages <- msg:
	fmt.Println("sent message", msg)
default:
	fmt.Println("no message sent")
}
select {
case msg := <-messages:
	fmt.Println("received message", msg)
case sig := <-signals:
	fmt.Println("received signal", sig)
default:
	fmt.Println("no activity")
}

sent message hi
received message hi

以上代码中,通道设置了缓冲区,第一个select的case代码块被执行;接收者也能接收通道发送的数据,第二个select的第一个case代码块被执行。

通道关闭

关闭通道表示不再有数据发送给通道。这有助于向通道的接收器传达信息,表明通道已完成。

jobs := make(chan int, 5)
done := make(chan bool)
go func() {
	for {
		j, more := <-jobs
		if more {
			fmt.Println("received job", j)
		} else {
			fmt.Println("received all jobs")
			done <- true
			return
		}
	}
}()
for j := 1; j <= 3; j++ {
	jobs <- j
	fmt.Println("sent job", j)
}
close(jobs)
fmt.Println("sent all jobs")
<-done

sent job 1
sent job 2
sent job 3
sent all jobs
received job 1
received job 2
received job 3
received all jobs

close方法调用时,通道向接收器发送信息,表明工作已完成。最后使用通道同步执行(变量名为done的通道),保证在多线程执行完成后,主线程才会结束。

注意,通道即使关闭,接收器依然可以接收通道的值。

通道遍历

range也可迭代获取通道接收的值。

queue := make(chan string, 2)
queue <- "one"
queue <- "two"
close(queue)
for elem := range queue {
	fmt.Println(elem)
}

one
two

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值