golang-goroutine和channel
goroutine
在go语言中,每一个并发的执行单元叫做一个goroutine
这里说到并发,所以先解释一下并发和并行的概念:
并发:逻辑上具备同时处理多个任务的能力
并行:物理上在同一时刻执行多个并发任务
当一个程序启动时,其主函数即在一个单独的goroutine中运行,一般这个goroutine是主goroutine
如果想要创建新的goroutine,只需要再执行普通函数或者方法的的前面加上关键字go
通过下面一个例子演示并发的效果,主goroutine会计算第45个斐波那契函数,在计算的同时会循环打印:-\|/
这里需要知道:当主goroutine结束之后,所有的goroutine都会被打断,程序就会退出
- package main
- import (
- "fmt"
- "time"
- )
- func spinner(delay time.Duration) {
- for {
- for _, r := range `-\|/` {
- fmt.Printf("\r%c", r)
- time.Sleep(delay)
- }
- }
- }
- func fib(n int) int {
- //斐波那契数列
- if n < {
- return n
- }
- return fib(n-) + fib(n-)
- }
- func main() {
- go spinner( * time.Millisecond)
- const n =
- fibN := fib(n)
- fmt.Printf("\rFib(%d)=%d\n", n, fibN)
- }
当第一次看到go的并发,感觉真是太好用了!!!!
所以在网络编程里,服务端都是需要同时可以处理很多个连接,我们看一下下面的服务端和客户端例子
服务端:
- package main
- import (
- "io"
- "log"
- "net"
- "time"
- )
- func handleConn(c net.Conn) {
- defer c.Close()
- _, err := io.WriteString(c, time.Now().Format("15:04:05\r\n"))
- if err != nil {
- return
- }
- time.Sleep( * time.Second)
- }
- func main() {
- listener, err := net.Listen("tcp", "localhost:8000")
- if err != nil {
- log.Fatal(err)
- return
- }
- for {
- conn, err := listener.Accept()
- if err != nil {
- log.Print(err)
- continue
- }
- go handleConn(conn)
- }
- }
客户端
- package main
- import (
- "io"
- "log"
- "net"
- "os"
- )
- func mustCopy(dst io.Writer, src io.Reader) {
- // 从连接中读取内容,并写到标准输出
- if _, err := io.Copy(dst, src); err != nil {
- log.Fatal(err)
- }
- }
- func main() {
- conn, err := net.Dial("tcp", "localhost:8000")
- if err != nil {
- log.Fatal(err)
- }
- mustCopy(os.Stdout, conn)
- }
Channel
channel是不同的goroutine之间的通信机制。
一个goroutine通过channel给另外一个goroutine发送信息。
每个channel 都有一个特殊的类型,也就是channel可以发送的数据的类型
我们可以通过make创建一个channel如:
ch := make(chan int) 这就是创建了一个类型为int的channel
默认我们这样创建的是无缓存的channel,当然我们可以通过第二个参数来设置容量
ch := make(chan int,10)
注意:channel是引用类型,channel的零值也是nil
两个相同类型的channel可以使用==运算符比较。如果两个channel引用的是相通的对象,那么比较的结
果为真。一个channel也可以和nil进行比较。
因为channel是在不同的goroutine之间进行通信的,所以channel这里有两种操作:存数据和取数据,而这里两种操作的
方法都是通过运算符:<-
ch <- x 这里是发送一个值x到channel中
x = <- ch 这里是从channel获取一个值存到变量x
<-ch 这里是从channel中取出数据,但是不使用结果
close(ch) 用于关闭channel
当我们关闭channel后,再次发送就会导致panic异常,但是如果之前发送过数据,我们在关闭channel之后依然可以执行接收操作
如果没有数据的话,会产生一个零值
基于channel发送消息有两个重要方面,首先每个消息都有一个值,但是有时候通讯的事件和发送的时刻也同样重要。
我们更希望强调通讯发送的时刻时,我们将它称为消息事件。有些消息并不携带额外的信息,它仅仅是用做两个goroutine之间的同步,这个时候我们可以用struct{}空结构体作为channel元素的类型
无缓存的channel
基于无缓存的channel的发送和接受操作将导致两个goroutine做一次同步操作,所以无缓存channel有时候也被称为同步channel
串联的channel (Pipeline)
channel也可以用于多个goroutine连接在一起,一个channel的输出作为下一个channel的输入,这种串联的channel就是所谓的pipeline
通过下面例子理解,第一个goroutine是一个计算器,用于生成0,1,2...形式的整数序列,然后通过channel将该整数序列
发送给第二个goroutine;第二个goroutine是一个求平方的程序,对收到的每个整数求平方,然后将平方后的结果通过第二个channel发送给第三个goroutine
第三个goroutine是一个打印程序,打印收到的每个整数
- package main
- import (
- "fmt"
- "time"
- )
- func main() {
- naturals := make(chan int)
- squares := make(chan int)
- go func() {
- for x := ;; x++ {
- naturals <- x
- }
- }()
- go func() {
- for {
- x := <-naturals
- squares <- x * x
- }
- }()
- for {
- fmt.Println(<-squares)
- time.Sleep( * time.Millisecond)
- }
- }
但是如果我把第一个生成数的写成一个有范围的循环,这个时候程序其实会报错的。
把for x := 0;; x++改成for x := 0; x < 100; x++,报错如下:fatal error: all goroutines are asleep - deadlock!
所以就需要想办法让发送知道没有可以发给channel的数据了,也让接受者知道没有可以接受的数据了
这个时候就需要用到close(chan)
当一个channel被关闭后,再向该channel发送数据就会导致panic异常
当从一个已经关闭的channel中接收数据,在接收完之前发送的数据后,并不会阻塞,而会立刻返回零值,所以在从channel里接受数据的时候可以多获取一个值如:
- go func(){
- for {
- x ,ok := <-naturals
- if !ok{
- break
- }
- squares <- x*x
- }
- close(squares)
- }()
- 第二位ok是一个布尔值,true表示成功从channel接受到值,false表示channel已经被关闭并且里面没有值可以接收
单方向的channel
当一个channel作为一个函数的参数时,它一般总是被专门用于只发送或者只接收
chan <- int :表示一个只发送int的channel,只能发送不能接收
< chan int : 表示一个只接受int的channel,只能接收不能发送
当然在有时候我们需要获取channel内部缓存的容量,可以通过内置的cap函数获取
而len函数则返回的是channel内实际有效的元素个数
基于select的多路复用
这里先说一个拥有的知识点:time.Tick函数
这个函数返回一个channel,通过下面代码进行理解:
- package main
- import (
- "fmt"
- "time"
- )
- func main() {
- tick := time.Tick( * time.Second)
- for countDown := ; countDown > ; countDown-- {
- j := <-tick
- fmt.Println(j)
- }
- }
程序会循环打印一个时间戳
select 语句:
- select {
- case <-ch1:
- // ...
- case x := <-ch2:
- // ...use x...
- case ch3 <- y:
- // ...
- default:
- // ...
- }
select语句的形式其实和switch语句有点类似,这里每个case代表一个通信操作
在某个channel上发送或者接收,并且会包含一些语句组成的一个语句块 。
select中的default来设置当其它的操作都不能够马上被处理时程序需要执行哪些逻辑
channel 的零值是nil, 并且对nil的channel 发送或者接收操作都会永远阻塞,在select语句中操作nil的channel永远都不会被select到。
这可以让我们用nil来激活或者禁用case,来达成处理其他输出或者输出时间超时和取消的逻辑
补充:channel 概念
类似unix中的管道pipe
先进先出
线程安全,多个goroutine同时访问,不需要加锁
channel是有类型的,一个整数的channel只能存放
定时器小例子:
- //定时器
- package main
- import (
- "time"
- "fmt"
- )
- func main() {
- t := time.NewTicker(time.Second)
- for v:= range t.C{
- fmt.Println("hello",v)
- }
- }
- // 一次性定时器
- package main
- import (
- "time"
- "fmt"
- )
- func main() {
- select{
- case <- time.After(time.Second):
- fmt.Println("after")
- }
- }
- //超时控制
package main- import (
- "time"
- "fmt"
- )
- func queryDb(ch chan int){
- time.Sleep(time.Second)
- ch <-
- }
- func main() {
- ch := make(chan int)
- go queryDb(ch)
- t := time.NewTicker(time.Second*)
- select{
- case v:=<-ch:
- fmt.Println("result:",v)
- case <-t.C:
- fmt.Println("timeout")
补充:不同的goroutine之间如何通信
首先我们能够想到的有:全局变量的方式,我们先通过这种本方法来演示:
- package main
- import (
- "time"
- "fmt"
- )
- var exits []bool
- func calc(index int){
- for i:=;i<;i++{
- time.Sleep(time.Millisecond)
- }
- exits[index] = true
- }
- func main() {
- start := time.Now().UnixNano()
- go calc()
- go calc()
- go calc()
- for{
- if exits[] && exits[] &&exits[]{
- break
- }
- }
- end := time.Now().UnixNano()
- fmt.Println("finished,const:%d ms",end-start)
- }
这种方法其实比较笨,go为我们提供了锁同步的方式 sync.WaitGroup,演示代码为:
- //等待一组goroutine执行完成
- package main
- import (
- "time"
- "fmt"
- "sync"
- )
- var waitGroup sync.WaitGroup
- func calc(index int){
- for i:=;i<;i++{
- time.Sleep(time.Millisecond)
- }
- //执行完成的时候Done
- waitGroup.Done()
- }
- func main() {
- start := time.Now().UnixNano()
- for i:=;i<;i++{
- // 每次在调用之前add
- waitGroup.Add()
- go calc(i)
- }
- //在循环外等待wait
- waitGroup.Wait()
- end := time.Now().UnixNano()
- fmt.Println("finished,const:%d ms",end-start)
- }
补充:关于单元测试和异常捕获
- package main
- import (
- "time"
- "fmt"
- )
- func calc(){
- // defer 定义的后面出现错误的都可以捕获到
- defer func() {
- err := recover()
- if err!=nil{
- fmt.Println(err)
- }
- }()
- var p *int
- *p =
- }
golang-goroutine和channel的更多相关文章
- golang goroutine的调度
golang goroutine的调度 1.什么是协程? 协程是一种用户态的轻量级线程. 2.进程.线程.协程的关系和区别: * 进程拥有自己独立的堆和栈,既不共享堆,亦不共享栈,进程由操作系统调度. ...
- goroutine和channel
近期在学习golang的goroutine和channel时候有一些疑惑: 带缓冲的channel和不带缓冲的channel有什么区别? goroutine和主进程的有哪些影响和关系? 多个gorou ...
- Go开发[八]goroutine和channel
进程和线程 进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位. 一个进程可以创 ...
- Go--关于 goroutine、channel
Go--关于 goroutine.channel goroutine 协程是一种轻量化的线程,由Go编译器进行优化. Go协程具有以下特点: 有独立的栈空间 共享程序堆中的空间 调度由用户控制 如果主 ...
- go并发之goroutine和channel,并发控制入门篇
并发的概念及其重要性 这段是简单科普,大佬可以跳过 并发:并发程序指同时进行多个任务的程序.在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行 ...
- TODO:Go语言goroutine和channel使用
TODO:Go语言goroutine和channel使用 goroutine是Go语言中的轻量级线程实现,由Go语言运行时(runtime)管理.使用的时候在函数前面加"go"这个 ...
- goroutine 加 channel 代替递归调用,突破递归调用的层级限制
package main import ( "fmt" "github.com/davecgh/go-spew/spew" "github.com/B ...
- Go基础--goroutine和channel
goroutine 在go语言中,每一个并发的执行单元叫做一个goroutine 这里说到并发,所以先解释一下并发和并行的概念: 并发:逻辑上具备同时处理多个任务的能力 并行:物理上在同一时刻执行多个 ...
- [转帖]go 的goroutine 以及 channel 的简介.
进程,线程的概念在操作系统的书上已经有详细的介绍.进程是内存资源管理和cpu调度的执行单元.为了有效利用多核处理器的优势,将进程进一步细分,允许一个进程里存在多个线程,这多个线程还是共享同一片内存空间 ...
- golang的缓冲channel简单使用
目录 golang的缓冲channel简单使用 阻塞型 非阻塞 golang的缓冲channel简单使用 我们常用的是无缓冲channel : make(chan type) 其实make() 创建c ...
随机推荐
- Sort Integers II
Given an integer array, sort it in ascending order. Use quick sort, merge sort, heap sort or any O(n ...
- Hbase(六) hbase Java API
一. 几个主要 Hbase API 类和数据模型之间的对应关系: 1. HBaseAdmin关系: org.apache.hadoop.hbase.client.HBaseAdmin作用:提供了一个接 ...
- Mysql(一) 基本操作
一.介绍 1.数据库 数据库,通俗的讲,即为存储数据的“仓库”.不过,数据库不仅只是存储,还对所存储的数据做相应的管理,例如,访问权限,安全性,并发操作,数据的备份与恢复,日志等.实际上,我们所提及的 ...
- Codeforces Round #441 Div. 2题解
比赛的时候E调了好久...F没时间写T T A:直接走到短的路上来回走就好了 #include<iostream> #include<cstring> #include< ...
- NOIP2010-2015后四题汇总
1.前言 正式开始的第一周的任务——把NOIP2010至NOIP2015的所有D1/2的T2/3写出暴力.共22题. 暴力顾名思义,用简单粗暴的方式解题,不以正常的思路思考.能够较好的保证正确性,但是 ...
- winform登录代码
Program.cs文件中 static class Program { /// <summary> /// 应用程序的主入口点. /// </summary> [STAThr ...
- bzoj 3834 [Poi2014]Solar Panels 数论分块
3834: [Poi2014]Solar Panels Time Limit: 20 Sec Memory Limit: 128 MBSubmit: 367 Solved: 285[Submit] ...
- Linux常用网络工具:批量主机服务扫描之nmap
Linux下有很多强大网络扫描工具,网络扫描工具可以分为:主机扫描.主机服务扫描.路由扫描等. 之前已经写过常用的主机扫描和路由扫描工具,nmap支持批量主机扫描和主机服务扫描. nmap的安装直接使 ...
- MySQL查询和修改auto_increment的方法
查询表名为tableName的auto_increment值: 复制代码 代码如下: SELECT AUTO_INCREMENT FROM information_schema.tables WHER ...
- 在xadmin中自定义内容的变量及优化汇总
在网上找了很多有关xadmin的内容,发现都不太全 ,找到一篇总结不错的 http://www.lybbn.cn/data/bbsdatas.php?lybbs=62 1.list_display 指 ...