Golang channel 的基本使用方法
Posted lowmanisbusy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Golang channel 的基本使用方法相关的知识,希望对你有一定的参考价值。
package main import ( "fmt" "learner/Add" "time" ) //a. 普通类型,普通变量保存的就是值,也叫值类型 //b. 获取普通变量的内存地址,用&,比如: var a int, 获取a的内存地址:&a //c. 指针类型,指针变量存的就是一个内存地址,这个地址指向值 //d. 获取指针类型所指向的值,使用:*,比如:var *p int, 使用*p获取p指向的值 //e. 将一个内存地址给一个指针类型进行赋值(不可以直接将变量赋值给指针,需要将内存地址赋值给指针): var a int=5, var p *int = &a // 在工程上有两种最常见的并发通信模型: 共享数据和消息通信, go语言选择后者,通过通信进行共享内存 // channel, goroutine 间的通信方式, 进程内的的通信.进程间的通信建议使用socket或者http等通信协议. // channel 是类型相关的, 一个channel只能传递一种指定类型的值, 这个值需要在声明channel时指定(可以理解为指定元素类型的管道) // 超时控制的经典实现 func chan_time_out_handler(ch chan int) (item bool){ // 使用 select 为channel实现超时机制, select的一个case必须是一个面向channel的操作 // 定义一个time_out chan timeOut := make(chan bool, 1) go func(){ time.Sleep(1e9) // 等待一秒钟 timeOut<- true }() // 利用time_out这个chan实现超时之后做何操作 select { case a := <- ch: // 尝试从ch这个chan中读取数据 fmt.Println(a) return true case <- timeOut: // 在等待时间范围内一直没有从ch中读取到了数据但是从time_out 这个 chan 中读取到了数据 return false } } // 只往chan中写入数据 func chan_in(ch_in chan<- int) { for i:=0; i <= 10; i++{ ch_in <- 1 } // 如果是使用range遍历chan, 那么当chan关闭时, 读取操作会立即结束,跳出循环(注意,这是channel中可能仍会存在数据), // channel关闭后,其实仍然可以从中读取已发送的数据(使用range无法实现, 可以使用常规的循环读取channel的方式),读取完数据后,将读取到零值,可以多次读取(仍然是零值) close(ch_in) // 当多个goroutine都使用了同一个channel时, 任何一个goroutine 中关闭了这个了这个channel, 其他goroutine将无法继续对这个channel进行读取 } // 只从chan读出数据 func chan_out(ch_out <-chan int) { // 使用range, 当这个channel关闭时,就会跳出循环,但是channel里面仍然可能存在数据 // x, ok := <- ch_out, 如果ok返回的是false,那么就表示这个chan已经关闭 for value := range ch_out{ fmt.Printf("+++++++++++++++++%d", value) } } func main() { // 切片和map 都是指针类型的数据, 指针类型的数据都可以使用make()进行分配内存 chs := make([]chan int, 10) // 定义一个切片并分配内存, 元素是chan类型, 这个chan内可以保存的元素为int类型, 该切片初始内存可以保存10个元素(不是channel的缓冲区, 是切片的初始大小) for i := 0; i < 10; i++ { chs[i] = make(chan int, 3) // 定义一个chan并分配内存, 缓冲区大小为3, 然后保存到切片中, 如果不设置缓冲区,当写入一个元素时,如果这个元素不被读取掉,写操作将会被阻塞 go Add.TestAddTwo(chs[i]) // 开启协程发送chan } for _, ch := range (chs) { fmt.Println("====================", len(ch)) //当程序运行到这里时, 这个channel有可能并没有写入数据,所以长度有可能为0 1 2 a := <-ch // 当这里从当前channel读取不到数据时就会阻塞 // b := <-ch // 继续读取, 读取不到就堵塞 fmt.Println(a) item := chan_time_out_handler(ch) fmt.Println(item) // 当channel写完数据操作完成后如果没有关闭,读取完数据,chan为空时,将会阻塞, 从而有可能造成死锁, 所以chan使用完必须关闭 } // 单向channel的实现,当需要将一个单向的channel从读变为写,或者从写变为读时,需要进行类型转换 // 第一个步,定义一个正常的channel ch_normal := make(chan int) // 第二步进行类型转换,将ch_normal 转换为只允许写的channel var ch_in chan<- int = ch_normal go chan_in(ch_in) // 第三步 生成一个只允许进行读的channel var ch_out <-chan int = ch_normal chan_out(ch_out) } // 当向一个channel写入数据, 在这个channel被读取前, 这个操作是阻塞的(在缓冲区写满之前, 即使没有读取操作,写操作都不会阻塞) // 当从一个channel读取数据时,在对应的channel写入数据前, 这个操作也是阻塞的,从而可以利用channel实现了类似锁的功能, 进而保证 // 了所有goroutine完成后主函数才返回 // 缓冲区满之后将会阻塞,除非有goroutine对其进行操作, 否则协程就会停留在向该channel写入元素的步骤上, 直到主进程退出, 向channel写入数据的协程也就退出. 协程的阻塞不影响主进程的执行 // 定义一个channel var chanName chan ElementType // 多层定义,例如定义一个 map, 键是string类型,元素是bool类型的channel: var myMap map[string] chan bool // 声明以后,定义一个channel 并赋值给变量: map["firstChan"] := make(chan false) , 使用内建函数make() // 如果是使用range遍历chan, 那么当chan关闭时, 读取操作会立即结束,跳出循环(注意,这是channel中可能仍会存在数据) // 当多个goroutine都使用了同一个channel时, 任何一个goroutine 中关闭了这个了这个channel, // 其他goroutine将无法继续对这个channel进行读取, 可以在主进程中进行守护, 等所有的goroutine执行完毕后再去关闭channel // close(chan) //关闭一个channel // 判断一个channel是否已关闭 //1. 如果channel已经关闭,继续往它发送数据会导致panic: send on closed channel //2. 关闭一个已经关闭的channel也会导致panic: close of closed channel func test2(ch chan int){ for{ if value,ok:=<-ch;ok{ //do somthing fmt.Print(value) }else{ break //ok 为false, 表示channel已经被关闭,退出循环 } } } // channel关闭后,仍然可以从中读取已发送的数据(使用range无法实现),读取完数据后,将读取到零值,可以多次读取。 func test1(){ ch:=make(chan int,3) ch<-3 ch<-2 ch<-1 close(ch) fmt.Print(<-ch) fmt.Print(<-ch) fmt.Print(<-ch) fmt.Print(<-ch) fmt.Print(<-ch) }
以上是关于Golang channel 的基本使用方法的主要内容,如果未能解决你的问题,请参考以下文章
Golang 并发: goroutine and channel
Golang 并发: goroutine and channel
Golang 并发: goroutine and channel