Skip to content

Commit

Permalink
.
Browse files Browse the repository at this point in the history
  • Loading branch information
webtao520 committed Nov 3, 2020
1 parent 60e6892 commit c6ca84d
Show file tree
Hide file tree
Showing 7 changed files with 248 additions and 0 deletions.
21 changes: 21 additions & 0 deletions Go语言并发/Go语言单向通道/1.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package main

import (
"fmt"
)

func main(){
ch:=make(chan int)
// 声明一个只能写入数据的通道类型,并赋值为ch
var chSendOnly chan<-int = ch
// 声明一个只能读取数据的通道类型,并赋值为ch
var chRecvOnly <-chan int =ch
}

/**
上面的例子中,chSendOnly 只能写入数据,如果尝试读取数据,将会出现如下报错:
invalid operation: <-chSendOnly (receive from send-only type chan<- int)
同理,chRecvOnly 也是不能写入数据的。
*/
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
Go语言的类型系统提供了单方向的 channel 类型,顾名思义,单向 channel 就是只能用于写入或者只能用于读取数据。
当然 channel 本身必然是同时支持读写的,否则根本没法用。

假如一个 channel 真的只能读取数据,那么它肯定只会是空的,因为你没机会往里面写数据。同理,
如果一个 channel 只允许写入数据,即使写进去了,也没有丝毫意义,因为没有办法读取到里面的数据。所谓的单向 channel 概念,其实只是对 channel 的一种使用限制。

### 单向通道的声明格式

我们在将一个 channel 变量传递到一个函数时,可以通过将其指定为单向 channel 变量,
从而限制该函数中可以对此 channel 的操作,比如只能往这个 channel 中写入数据,或者只能从这个 channel 读取数据。

单向 channel 变量的声明非常简单,只能写入数据的通道类型为chan<-,只能读取数据的通道类型为<-chan,格式如下:
var 通道实例 chan<- 元素类型 // 只能写入数据的通道
var 通道实例 <-chan 元素类型 // 只能读取数据的通道

元素类型:通道包含的元素类型。
通道实例:声明的通道变量。

### 单向通道的使用例子
示例代码如下:
+ 案例
* 1.go

### time包中的单向通道

time 包中的计时器会返回一个 timer 实例,代码如下:

timer := time.NewTimer(time.Second)
timer的Timer类型定义如下:

type Timer struct {
C <-chan Time
r runtimeTimer
}
第 2 行中 C 通道的类型就是一种只能读取的单向通道。如果此处不进行通道方向约束,一旦外部向通道写入数据,将会造成其他使用到计时器的地方逻辑产生混乱。

因此,单向通道有利于代码接口的严谨性。


### 关闭 channel

关闭 channel 非常简单,直接使用Go语言内置的 close() 函数即可:
close(ch)

在介绍了如何关闭 channel 之后,我们就多了一个问题:如何判断一个 channel 是否已经被关闭?我们可以在读取的时候使用

### 多重返回值的方式:

x, ok := <-ch

这个用法与 map 中的按键获取 value 的过程比较类似,只需要看第二个 bool 返回值即可,如果返回值是 false 则表示 ch 已经被关闭。
Empty file.
85 changes: 85 additions & 0 deletions Go语言并发/Go语言无缓冲的通道/1.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// 这个示例程序展示如何用无缓冲的通道来模拟
// 2 个goroutine 间的网球比赛
package main

import (
"fmt"
"math/rand"
"sync"
"time"
)

// wg 用来等待程序结束
var wg sync.WaitGroup

func init() {
rand.Seed(time.Now().UnixNano())
}

// main 是所有Go 程序的入口
func main() {
// 创建一个无缓冲的通道
court := make(chan int)

// 计数加 2,表示要等待两个goroutine
wg.Add(2)

// 启动两个选手
go player("Nadal", court)
go player("Djokovic", court)

// 发球
court <- 1

// 等待游戏结束
wg.Wait()
}

// player 模拟一个选手在打网球
func player(name string, court chan int) {
// 在函数退出时调用Done 来通知main 函数工作已经完成
defer wg.Done()

for {
// 等待球被击打过来
ball, ok := <-court
if !ok {
// 如果通道被关闭,我们就赢了
fmt.Printf("Player %s Won\n", name)
return
}

// 选随机数,然后用这个数来判断我们是否丢球
n := rand.Intn(100)
if n%13 == 0 {
fmt.Printf("Player %s Missed\n", name)

// 关闭通道,表示我们输了
close(court)
return
}

// 显示击球数,并将击球数加1
fmt.Printf("Player %s Hit %d\n", name, ball)
ball++

// 将球打向对手
court <- ball
}
}



/**
PS D:\goLang\github\golang_project\Go语言并发\Go语言无缓冲的通道> go run 1.go
Player Djokovic Hit 1
Player Nadal Hit 2
Player Djokovic Hit 3
Player Nadal Hit 4
Player Djokovic Hit 5
Player Nadal Hit 6
Player Djokovic Hit 7
Player Nadal Hit 8
Player Djokovic Missed
Player Nadal Won
*/
65 changes: 65 additions & 0 deletions Go语言并发/Go语言无缓冲的通道/2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// 这个示例程序展示如何用无缓冲的通道来模拟
// 4 个goroutine 间的接力比赛
package main

import (
"fmt"
"sync"
"time"
)

// wg 用来等待程序结束
var wg sync.WaitGroup

// main 是所有Go 程序的入口
func main() {
// 创建一个无缓冲的通道
baton := make(chan int)

// 为最后一位跑步者将计数加1
wg.Add(1)

// 第一位跑步者持有接力棒
go Runner(baton)

// 开始比赛
baton <- 1

// 等待比赛结束
wg.Wait()
}

// Runner 模拟接力比赛中的一位跑步者
func Runner(baton chan int) {
var newRunner int

// 等待接力棒
runner := <-baton

// 开始绕着跑道跑步
fmt.Printf("Runner %d Running With Baton\n", runner)

// 创建下一位跑步者
if runner != 4 {
newRunner = runner + 1
fmt.Printf("Runner %d To The Line\n", newRunner)
go Runner(baton)
}

// 围绕跑道跑
time.Sleep(100 * time.Millisecond)

// 比赛结束了吗?
if runner == 4 {
fmt.Printf("Runner %d Finished, Race Over\n", runner)
wg.Done()
return
}

// 将接力棒交给下一位跑步者
fmt.Printf("Runner %d Exchange With Runner %d\n",
runner,
newRunner)

baton <- newRunner
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Go语言中无缓冲的通道(unbuffered channel)是指在接收前没有能力保存任何值的通道。
这种类型的通道要求发送 goroutine 和接收 goroutine 同时准备好,才能完成发送和接收操作。

如果两个 goroutine 没有同时准备好,通道会导致先执行发送或接收操作的 goroutine 阻塞等待。
这种对通道进行发送和接收的交互行为本身就是同步的。其中任意一个操作都无法离开另一个操作单独存在。

阻塞指的是由于某种原因数据没有到达,当前协程(线程)持续处于等待状态,直到条件满足才解除阻塞。

同步指的是在两个或多个协程(线程)之间,保持数据内容一致性的机制。

下图展示两个 goroutine 如何利用无缓冲的通道来共享一个值。
【示例 1】在网球比赛中,两位选手会把球在两个人之间来回传递。选手总是处在以下两种状态之一,要么在等待接球,要么将球打向对方。
可以使用两个 goroutine 来模拟网球比赛,并使用无缓冲的通道来模拟球的来回,代码如下所示。
+ 案例
* 1.go

【示例 2】用不同的模式,使用无缓冲的通道,在 goroutine 之间同步数据,来模拟接力比赛。在接力比赛里,4 个跑步者围绕赛道轮流跑。
第二个、第三个和第四个跑步者要接到前一位跑步者的接力棒后才能起跑。比赛中最重要的部分是要传递接力棒,要求同步传递。在同步接力棒的时候,参与接力的两个跑步者必须在同一时刻准备好交接。代码如下所示。
+ 案例
* 2.go
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,12 @@


### Go语言并发
* 并发和并行的区别
* Go语言单向通道
* Go语言单向通道
* Go语言轻量级线程
* Go语言通道(chan)
* Go语言无缓冲的通道

### ETCD

Expand Down

0 comments on commit c6ca84d

Please sign in to comment.