Channel
1、Go语言不建议我们使用锁机制来解决多线程问题、建议我们使用通道。
2、通信的角色,必须在2个以上。1个人,不能叫做通信。
3、chan ,必须要作用在两个及两个以上的 goroutine .
4、一个goroutine需要将一些信息告诉另外一个goroutine ,就直接将数据信息放入chan即可。
通道:可以被认为是 Gr 通信管道。
类似于水管,数据可以从一端流到另一端。
不要通过共享内存来通信,而应该通过通信来共享内存(chan)
// chan 类型 ,通道
var a chan int
a = make(chan int)
// 使用规则(存 chan<-、取 <-chan)
a <- 1
data := <- a
package main
import (
"fmt"
"time"
)
// 定义通道 chan
// 这个 goroutine 希望告诉 main 线程,我还没结束。(通信)
func main() {
// 定一个bool的通道
var ch chan bool
ch = make(chan bool)
// 在一个goroutine中去往通道中放入数据
go func() {
for i := 0; i < 10; i++ {
fmt.Println("goroutine-", i)
}
time.Sleep(time.Second * 3)
ch <- true
}()
// 另一个goroutine可以从通道中取出数据。(线程之间的通信)
// 阻塞等待ch拿到值。有另外一个goroutine往里放值。
data := <-ch
fmt.Println("ch data:", data)
}
一个通道发送和接收数据,默认是阻塞的。
当一个数据被发送到通道时,在发送语句中被阻塞,直到另一个Goroutine从该通道读取数据。
相对地,当从通道读取数据时,读取被阻塞,直到一个Goroutine将数据写入该通道。
本身channel就是同步的, 意味着同一时间,只能有一条goroutine来操作。
最后:通道是goroutine之间的连接,所有通道的发送和接收必须处在不同的goroutine中。
这些通道的特性是帮助Goroutines有效地进行通信,而无需像使用其他编程语言中非常常见的显式锁或条件变量。
死锁
如果创建了chan,没有 Goroutine 来使用了,则会出现死锁。
使用通道时要考虑的一一个重要因素是死锁。如果Goroutine在一 个通道 上发送数据,那么预计其他的Goroutine应该接收数据。如果这种情况不发生,那么程序将在运行时出现死锁。
类似地,如果Goroutine 正在等待从通道接收数据,那么另一些Goroutine将会在该通道上写入数据,否则程序将会死锁。
package main
import (
"fmt"
)
// 定义通道 chan
// 这个 goroutine 希望告诉 main 线程,我还没结束。(通信)
func main() {
// 定一个bool的通道
var ch chan bool
ch = make(chan bool)
//// 在一个goroutine中去往通道中放入数据
go func() {
for i := 0; i < 10; i++ {
fmt.Println("goroutine-", i)
}
//time.Sleep(time.Second * 3)
ch <- true
}()
// 定义好通道之后,如果没有 goroutine来使用(必须在两个及以上goroutine),那么就会产生死锁
// deadlock!
data := <-ch
fmt.Println("ch data:", data)
// 死锁的产生,没有goroutine来消耗通道(存取)
ch2 := make(chan int)
ch2 <- 10
}
死锁的其他情况:blog.csdn.net/liu19721018…
1、单线的使用,没有其他的goroutine消费
2、两个chan,互相需要对方的数据,但是由于判断,拿不到对方的数据。
3、sync 锁产生的死锁。
关闭通道
package main
import (
"fmt"
"time"
)
// 关闭通道
// 告诉接收方,我不会再有其他数据发送到chan了。
func main() {
// 在main线程中定义的通道
ch1 := make(chan int)
go test7(ch1)
// 读取chan中的数据
for {
time.Sleep(time.Second)
// ok 判断chan的状态是否是关闭,如果是关闭,不会再取值了。
// ok, 如果是true,就代表我们还在读数据
// ok, 如果是fasle,就说明该通道已关闭
data, ok := <-ch1
if !ok {
fmt.Println("读取完毕", ok)
break
}
fmt.Println("ch1 data:", data)
}
}
// 通道可以参数传递
func test7(ch chan int) {
for i := 0; i < 10; i++ {
ch <- i
}
// 关闭通道,告诉接收方,不会在往ch中放入数据
close(ch)
}
通过ok来判断是否读取完毕数据。
for range 可以简化开发
package main
import (
"fmt"
"time"
)
// 关闭通道
// 告诉接收方,我不会再有其他数据发送到chan了。
func main() {
// 在main线程中定义的通道
ch1 := make(chan int)
go test7(ch1)
// 读取chan中的数据, for 一个个取,并且会自动判断chan是否close 迭代器
for data := range ch1 {
time.Sleep(time.Second)
fmt.Println(data)
}
fmt.Println("end")
}
// 通道可以参数传递
func test7(ch chan int) {
for i := 0; i < 10; i++ {
ch <- i
}
// 关闭通道,告诉接收方,不会在往ch中放入数据
close(ch)
}
缓冲通道(chan)
非缓冲通道
chan , 只能存放一个数据,发送和接受都是阻塞的。一次发送对应一个接收。
缓冲通道
通道带了一个缓冲区,发送的数据直到缓冲区填满为止,才会被阻塞,接收的也是,只有缓冲区清空,才会阻塞。
chan如果只有一个容量,老是阻塞,效率是很低的。
package main
import (
"fmt"
"strconv"
"time"
)
// 缓冲通道 chan,cap
func main() {
// 非缓冲通道
ch1 := make(chan int)
fmt.Println(cap(ch1), len(ch1)) // 0 0
//ch1 <- 100
// 缓冲通道
// 缓冲区通道,放入数据,不会产生死锁,它不需要等待另外的线程来拿,它可以放多个数据。
// 如果缓冲区满了,还没有人取,也会产生死锁。
ch2 := make(chan string, 5)
fmt.Println(cap(ch2), len(ch2)) // 5 0
ch2 <- "1"
fmt.Println(cap(ch2), len(ch2)) // 5 1 , 可以通过len来判断缓冲通道中的数据数量
ch2 <- "2"
ch2 <- "3"
fmt.Println(cap(ch2), len(ch2)) // 5 3
ch2 <- "4"
ch2 <- "5"
fmt.Println(cap(ch2), len(ch2)) // 5 5
data := <-ch2
ch2 <- "6" // deadlock!
fmt.Println(data)
ch3 := make(chan string, 4)
go test8(ch3)
fmt.Println("--------------------------")
for s := range ch3 {
time.Sleep(time.Second)
fmt.Println("main中读取的数据:", s)
}
fmt.Println("main-end")
}
func test8(ch chan string) {
for i := 0; i < 10; i++ {
ch <- "test - " + strconv.Itoa(i)
fmt.Println("子goroutine放入数据:", "test - "+strconv.Itoa(i))
}
close(ch)
}
缓冲通道,可以定义缓冲区的数量
如果缓冲区没有满,可以继续存放,如果满了,也会阻塞等待
如果缓冲区空的,读取也会等待,如果缓冲区中有多个数据,依次按照先进先出的规则进行读取。
如果缓冲区满了,同时有两个线程在读或者写,这个时候和普通的chan一样。一进一出。
定向通道
双向通道
channel 是用来实现 goroutine 通信的。一个写、一个读、这是双向通道。
ch <- data
data := <- ch
单向通道 只能读或者只能写
package main
import (
"fmt"
"time"
)
// 单向通道使用场景
func main() {
ch1 := make(chan int) // 可读可写
go writeOnly(ch1)
go readOnly(ch1)
time.Sleep(time.Second * 3)
}
// 作为函数的参数或者返回值之类的。
// 指定函数去写,不让他读取,防止通道滥用
func writeOnly(ch chan<- int) {
// 函数的内部,处理一些写数据的操作
ch <- 100
}
// 指定函数去读,不让他写,防止通道滥用
func readOnly(ch <-chan int) int {
// 取出通道的值,做一些操作,不可写的。
data := <-ch
fmt.Println(data)
return data
}