《Effective Go》有一句话很好地说明了Go语言的并发实现理念:“不要通过共享内在来通信,而通过通信来共享内存。”
通道的创建语法如下:
c:=make(chan string)
使用内置函数make创建一个通道,这是使用关键字chan指定的。
向通道发送消息的语法如下:
c<="Hello World"
从通道那里接收消息的语法如下:
msg:=<-c
要从通道那里接收消息,需要在<-后面加上通道名。可使用简短变量赋值,将来自通道的消息直接赋给变量。箭头向左表示数据离开通道(接收),箭头向右表示数据进入通道(发送)。
使用通道进行通信,程序清单如下:(代码错误,没搞清楚。)
package main
import(
"fmt"
"time"
)
func slowFunc(){
time.Sleep(time.Second*2)
c <- "slowFunc() finished"
}
func main(){
c:=make(chan string)
go slowFunc()
msg:=<-c
fmt.Println(msg)
}
运行结果如下:
# command-line-arguments
./main.go:9:3: undefined: c
让缓冲通道接收两条消息,程序清单如下:
package main
import(
"fmt"
"time"
)
func receiver(c chan string){
for msg:=range c{
fmt.Println(msg)
}
}
func main(){
messages:=make(chan string,2)
messages<-"hello"
messages<-"world"
close(messages)
fmt.Println("Pushed two messages onto Channel with no receivers")
time.Sleep(time.Second*1)
receiver(messages)
}
运行结果如下:
Pushed two messages onto Channel with no receivers
hello
world
通道和流程控制,程序清单如下:
package main
import(
"fmt"
"time"
)
func pinger(c chan string){
t:=time.NewTicker(1*time.Second)
for{
c<-"ping"
<-t.C
}
}
func main(){
messages:=make(chan string)
go pinger(messages)
msg:=<-messages
fmt.Println(msg)
}
运行结果如下:
ping
不断地执行指定的操作,程序清单如下:
package main
import(
"fmt"
"time"
)
func pinger(c chan string){
t:=time.NewTicker(1*time.Second)
for{
c<-"ping"
<-t.C
}
}
func main(){
messages:=make(chan string)
go pinger(messages)
for{
msg:=<-messages
fmt.Println(msg)
}
}
运行结果如下:
不断地出现ping,每一个ping的间隔有一些时间。死循环。
代码如下:
package main
import(
"fmt"
"time"
)
func pinger(c chan string){
t:=time.NewTicker(1*time.Second)
for{
c<-"ping"
<-t.C
}
}
func main(){
messages:=make(chan string)
go pinger(messages)
for i:=0;i<5;i++{
msg:=<-messages
fmt.Println(msg)
}
}
运行结果如下:(有时间间隔)
ping
ping
ping
ping
ping
select语句,程序清单如下:
package main
import(
"fmt"
"time"
)
func ping1(c chan string){
time.Sleep(time.Second*1)
c<-"ping on channel1"
}
func ping2(c chan string){
time.Sleep(time.Second*2)
c<-"ping on channel2"
}
func main(){
channel1:=make(chan string)
channel2:=make(chan string)
go ping1(channel1)
go ping2(channel2)
select{
case msg1:=<-channel1:
fmt.Println("received",msg1)
case msg2:=<-channel2:
fmt.Println("received",msg2)
}
}
运行结果如下:
received ping on channel1
代码如下:
package main
import(
"fmt"
"time"
)
func ping1(c chan string){
time.Sleep(time.Second*3)
c<-"ping on channel1"
}
func ping2(c chan string){
time.Sleep(time.Second*2)
c<-"ping on channel2"
}
func main(){
channel1:=make(chan string)
channel2:=make(chan string)
go ping1(channel1)
go ping2(channel2)
select{
case msg1:=<-channel1:
fmt.Println("received",msg1)
case msg2:=<-channel2:
fmt.Println("received",msg2)
}
}
运行结果如下:
received ping on channel2
给select语句指定超时时间,代码如下:
package main
import(
"fmt"
"time"
)
func ping1(c chan string){
time.Sleep(time.Second*3)
c<-"ping on channel1"
}
func ping2(c chan string){
time.Sleep(time.Second*2)
c<-"ping on channel2"
}
func main(){
channel1:=make(chan string)
channel2:=make(chan string)
go ping1(channel1)
go ping2(channel2)
select{
case msg1:=<-channel1:
fmt.Println("received",msg1)
case msg2:=<-channel2:
fmt.Println("received",msg2)
case <-time.After(500*time.Millisecond):
fmt.Println("no message received. giving up.")
}
}
运行结果如下:
no message received. giving up.
使用退出通道,程序清单如下:
package main
import(
"fmt"
"time"
)
func sender(c chan string){
t:=time.NewTicker(1*time.Second)
for{
c<-"I'm sending a message"
<-t.C
}
}
func main(){
messages:=make(chan string)
stop:=make(chan bool)
go sender(messages)
go func(){
time.Sleep(time.Second*2)
fmt.Println("Time's up!")
stop<-true
}()
for{
select{
case <-stop:
return
case msg:=<-messages:
fmt.Println(msg)
}
}
}
运行结果如下:(除了倒数两行,其它都有时间间隔。)
I'm sending a message
I'm sending a message
I'm sending a message
Time's up!
或者:
I'm sending a message
I'm sending a message
Time's up!
通道(channel)——在多个goroutine间通信的管道
一、通道的特征
二、声明通道类型
声明如下:
var 通道变量 chan 通道类型
通道类型:通道内的数据类型。
通道变量:保存通道的变量。
chan类型的空值是nil,声明后需要配合make后才能使用。
三、创建通道
通道是引用类型,需要使用make进行创建,格式如下:
通道实例:=make(chan 数据类型)
数据类型:通道内传输的元素类型。
通道实例:通过make创建的通道句柄。
四、使用通道发送数据
1、通道发送数据的格式
通道的发送使用特殊的操作符“<-”,将数据通过通道发送的格式为:
通道变量<-值
通道变量:通过make创建好的通道实例。
值:可以是变量、常量、表达式或者函数返回值等。值的类型必须与ch通道的元素类型一致。
2、通过通道发送数据的例子
使用make创建一个通道后,就可以使用“<-”向通道发送数据。
3、发送将持续阻塞直到数据被接收
把数据往通道中发送时,如果接收方一直都没有接收,那么发送操作将持续阻塞。
五、使用通道接收数据
1、阻塞接收数据
格式如下:
data:=<-ch
2、非阻塞接收数据
格式如下:
data,ok:=<-ch
data:表示接收到的数据。未接收到数据时,data为通道类型的零值。
ok:表示是否接收到数据。
3、接收任意数据,忽略接收的数据
格式如下:
<-ch
代码如下:
package main
import (
"fmt"
)
func main() {
//构建一个通道
ch := make(chan int)
//开启一个并发匿名函数
go func() {
fmt.Println("start goroutine")
//通过通道通知main的goroutine
ch <- 0
fmt.Println("exit goroutine")
}()
fmt.Println("wait goroutine")
//等待匿名goroutine
<-ch
fmt.Println("all done")
}
运行结果如下:
wait goroutine
start goroutine
exit goroutine
all done
4、循环接收
代码如下:
package main
import (
"fmt"
"time"
)
func main() {
//构建一个通道
ch := make(chan int)
//开启一个并发匿名函数
go func() {
//从3循环到0
for i := 3; i >= 0; i-- {
//发送3到0之间的数值
ch <- i
//每次发送完时等待
time.Sleep(time.Second)
}
}()
//遍历接收通道数据
for data := range ch {
//打印通道数据
fmt.Println(data)
//当遇到数据0时,退出接收循环
if data == 0 {
break
}
}
}
运行结果如下:
3
2
1
0
六、示例:并发打印
代码如下:
package main
import (
"fmt"
)
func printer(c chan int) {
//开始无限循环等待数据
for {
//从channel中获取一个数据
data := <-c
//将0视为数据结束
if data == 0 {
break
}
//打印数据
fmt.Println(data)
}
//通知main已经结束循环(我搞定了!)
c <- 0
}
func main() {
//创建一个channel
c := make(chan int)
//并发执行printer,传入channel
go printer(c)
for i := 1; i <= 10; i++ {
//将数据通过channel投送给printer
c <- i
}
//通知并发的printer结束循环(没数据啦!)
c <- 0
//等待printer结束(搞定咕我!)
<-c
}
运行结果如下:
1
2
3
4
5
6
7
8
9
10
七、单向通道——通道中的单行道
1、单向通道的声明格式
只能发送的通道类型为chan<-,只能接收的通道类型为<-chan,格式如下
var 通道实例 chan<- 元素类型
var 通道实例 <-chan 元素类型
元素类型:通道包含的元素类型
通道实例:声明的通道变量。
2、单向通道的使用例子
3、time包中的单向通道
time包中的计时器会返回一个timer实例,代码如下:
timer:=time.NewTimer(time.Second)
八、带缓冲的通道
1、创建带缓冲的通道
通道实例:=make(chan 通道实例,缓冲大小)
通道类型:和无缓冲通道用法一致,影响通道发送和接收的数据类型。
缓冲大小:决定通道最多可以保存的元素数量。
通道实例:被创建出的通道实例。
代码如下:
package main
import (
"fmt"
)
func main() {
//创建一个3个元素缓冲大小的整型通道
ch := make(chan int, 3)
//查看当前通道的大小
fmt.Println(len(ch))
//发送3个整形元素的通道
ch <- 1
ch <- 2
ch <- 3
//查看当前通道的大小
fmt.Println(len(ch))
}
运行结果如下:
PS C:\Users\a-xiaobodou\OneDrive - Microsoft\Projects\Go> go run main.go
0
3
2、阻塞条件
(1)带缓冲通道被填满时,尝试再次发送数据时发生阻塞。
(2)带缓冲通道为空时,尝试接收数据 时发生阻塞。
九、通道的多路复用——同时处理接收和发送多个通道的数据
select{
case 操作1:
响应操作1
case 操作2:
响应操作2
……
default:
没有操作情况
}
十、示例:模拟远程过程调用(RPC)
代码如下:
package main
import (
"errors"
"fmt"
"time"
)
// 模拟RPC客户端的请求和接收消息封装
func RPCClient(ch chan string, req string) (string, error) {
// 向服务器发送请求
ch <- req
// 等待服务器返回
select {
case ack := <-ch: // 接收到服务器返回数据
return ack, nil
case <-time.After(time.Second): // 超时
return "", errors.New("Time out")
}
}
// 模拟RPC服务器端接收客户端请求和回应
func RPCServer(ch chan string) {
for {
// 接收客户端请求
data := <-ch
// 打印接收到的数据
fmt.Println("server received:", data)
// 反馈给客户端收到
ch <- "roger"
}
}
func main() {
// 创建一个无缓冲字符串通道
ch := make(chan string)
// 并发执行服务器逻辑
go RPCServer(ch)
// 客户端请求数据和接收数据
recv, err := RPCClient(ch, "hi")
if err != nil {
// 发生错误打印
fmt.Println(err)
} else {
// 正常接收到数据
fmt.Println("client received", recv)
}
}
运行结果如下:
[Running] go run "c:\Users\a-xiaobodou\OneDrive - Microsoft\Projects\Go\main.go"
server received: hi
client received roger
[Done] exited with code=0 in 2.429 seconds
十一、示例:使用通道响应计时器的事件
1、一段时间之后 (time.After)
代码如下:
package main
import (
"fmt"
"time"
)
func main() {
// 声明一个退出用的通道
exit := make(chan int)
// 打印开始
fmt.Println("start")
// 过1秒后,调用匿名函数
time.AfterFunc(time.Second, func() {
// 1秒后,打印结果
fmt.Println("one second after")
// 通知main的goroutine已经结束
exit <- 0
})
// 等待结束
<-exit
}
运行结果如下:
[Running] go run "c:\Users\a-xiaobodou\OneDrive - Microsoft\Projects\Go\main.go"
start
one second after
[Done] exited with code=0 in 2.205 seconds
2、定点计时
代码如下:
package main
import (
"fmt"
"time"
)
func main() {
// 创建一个断续器,每500毫秒触发一次
ticker := time.NewTicker(time.Millisecond * 500)
// 创建一个计时器,2秒后触发
stopper := time.NewTimer(time.Second * 2)
// 声明计数变量
var i int
// 不断的检查通道情况
for {
// 多路复用通道
select {
case <-stopper.C: // 计时器到时了
fmt.Println("stop")
// 跳出循环
goto StopHere
case <-ticker.C: // 断续器触发了
// 记录触发了多少次
i++
fmt.Println("tick", i)
}
}
// 退出的标签,使用goto跳转
StopHere:
fmt.Println("done")
}
运行结果如下:
[Running] go run "c:\Users\a-xiaobodou\OneDrive - Microsoft\Projects\Go\main.go"
tick 1
tick 2
tick 3
tick 4
stop
done
[Done] exited with code=0 in 5.162 seconds
十二、关闭通道后继续使用通道
1、格式
使用close()来关闭一个通道:
close(ch)
2、给被关闭通道发送数据将会触发panic
代码如下:
package main
import (
"fmt"
)
func main() {
// 创建一个整型的胶片
ch := make(chan int)
//关闭通道
close(ch)
//打印通道的指针,容量和长度
fmt.Printf("ptr:%p cap:%d len:%d\n", ch, cap(ch), len(ch))
//给关闭的通道发送数据
ch <- 1
}
运行结果如下:
[Running] go run "c:\Users\a-xiaobodou\OneDrive - Microsoft\Projects\Go\main.go"
ptr:0xc000086060 cap:0 len:0
panic: send on closed channel
goroutine 1 [running]:
main.main()
c:/Users/a-xiaobodou/OneDrive - Microsoft/Projects/Go/main.go:19 +0x105
exit status 2
[Done] exited with code=1 in 1.243 seconds
3、从已关闭的通道接收数据时将不会发生阻塞
代码如下:
package main
import "fmt"
func main() {
// 创建一个整形带2个缓冲的通道
ch := make(chan int, 2)
// 给通道放入两个数据
ch <- 0
ch <- 1
// 关闭缓冲
close(ch)
// 遍历缓冲所有数据,且多遍历1个
for i := 0; i < cap(ch)+1; i++ {
// 从通道中取出数据
v, ok := <-ch
// 打印取出数据的状态
fmt.Println(v, ok)
}
}
运行结果如下:
[Running] go run "c:\Users\a-xiaobodou\OneDrive - Microsoft\Projects\Go\main.go"
tick 1
tick 2
tick 3
tick 4
stop
done
[Done] exited with code=0 in 5.162 seconds
示例:Telnet回音服务器——TCP服务器的基本结构
同步——保证并发环境下数据访问的正确性
本文深入探讨Go语言中的并发编程技巧与通道的使用方法,包括通道的基本概念、声明与创建,以及如何通过通道实现进程间的通信。文章还介绍了通道在实际应用中的多种场景,并通过具体示例展示了如何有效利用通道进行数据传输。
203

被折叠的 条评论
为什么被折叠?



