在早期,CPU都是以单核的形式顺序执行机器指令。C语言、PHP正是这种顺序编程语言的表明,即全部的指令都是以串行的方式执行,在相同的时刻有且仅有一个CPU在顺序执行程序的指令。随着处理器技术的发展,单核时代以提高处理器频率来提升运行效率的方式遇到了瓶颈。单核CPU的发展的停滞,给多核CPU的发展带来了机遇。相应地,编程语言也开始逐步向并行化的方向发展。Go语言正是在多核和网络化的时代背景下诞生的原生支持并发的编程语言。编程
goroutine 是 Go 语言特有的并发体,是一种轻量级的线程,由go关键字启动。在真实的Go语言的实现中,goroutine 和系统线程也不是等价的。尽管二者的区别实际上只是一个量的区别,但正是这个量变引起了 Go 语言并发编程质的飞跃。网络
package main import "fmt" func main() { //并发版hello world go println("hello world") }
每一个系统级线程都会有一个固定大小的栈(通常默承认能是8MB),这个栈主要用来保存函数递归调用时参数和局部变量。固定了栈的大小致使了两个问题:一是对于不少只须要很小的栈空间的线程来讲是一个巨大的浪费,二是对于少数须要巨大栈空间的线程来讲又面临栈溢出的风险。相反,一个 goroutine 会以一个很小的栈启动(多是2KB或4KB),当遇到当前栈空间不足时, goroutine 会根据须要动态地伸缩栈的大小。由于启动的代价很小,因此咱们能够轻易地启动成千上万个 goroutine 。
Go的调度器使用了一些技术手段,能够在n个操做系统线程上多工调度m个 goroutine 。只有在当前 goroutine 发生阻塞时才会致使调度,同时发生在用户态,切换的代价要比系统线程低得多。运行时有一个 runtime.GOMAXPROCS
变量,用于控制当前运行正常非阻塞 goroutine 的系统线程数目。在Go语言中启动一个 goroutine 不只和调用函数同样简单,并且 goroutine 之间调度代价也很低,这些因素极大地促进了并发编程的流行和发展。闭包
在并发编程中,对共享资源的正确访问须要精确的控制,在目前的绝大多数语言中,都是经过加锁等线程同步方案来解决这一问题。而Go语言却另辟蹊径,它将共享的值经过Channel传递,数据竞争从设计层面上就被杜绝了。经过通道来传值是Go语言推荐的作法,虽然像引用计数这类简单的并发问题经过原子操做或互斥锁就能很好地实现,可是经过Channel来控制访问可以让你写出更简洁正确的程序。并发
//非缓冲通道 ch1 := make(chan int) //缓冲通道 ch2 := make(chan int, 1)
非缓冲通道必须确保有协程正在尝试读取当前通道,不然写操做就会阻塞直到有其它协程来从通道中读东西。tcp
//从通道读, data, ok := <-ch1 data := <-ch1 //往通道写 ch2 <-data //使用range读,通道没数据for就会阻塞,通道关闭就会退出for for v := range ch1 { println(v) } //多路通道 for { select { case v := <-ch1: println(v) case v := <-ch2: println(v) } }
通道满了,写操做就会阻塞,协程就会进入休眠,直到有其它协程读通道挪出了空间,协程才会被唤醒。通道空了,读操做就会阻塞,协程也会进入睡眠,直到有其它协程写通道装进了数据才会被唤醒。编程语言
//关闭通道 close(ch1)
读取一个已经关闭的通道会当即返回通道类型的「零值」,而写一个已经关闭的通道会抛异常。使用 for range
读取时用完要记得关闭通道,不然会阻塞。函数
根据 Go 语言规范,main 函数退出时程序结束,不会等待任何后台线程。由于 goroutine 的执行和 main 函数的返回事件是并发的,谁都有可能先发生,因此何时打印,可否打印都是未知的。操作系统
func main() { go println("你好, 世界") time.Sleep(time.Second) //或者一个死循环 for {} }
不可靠,由于实际协程执行时间未知线程
func main() { var mu sync.Mutex mu.Lock() go func() { println("你好, 世界") mu.Unlock() }() mu.Lock() }
主携程中第二次获取锁时阻塞设计
func main() { ch := make(chan int, 1) go func() { println("你好, 世界") ch<-1 }() <-ch }
从ch取值,因为通道为空因此会阻塞直到有数据写入
func main() { var wg sync.WaitGroup wg.Add(10) for i := 1; i < 10; i++ { //wg.Add(1) go func(n int) { println("你好, ", n) wg.Done() //wg.Add(-1) }(i) } //等待协程完成 wg.Wait() }
若是不把i做为参数传入闭包函数,闭包go协程里面引用的是变量i的地址,全部的go协程启动后等待调用,极可能在for循环完成以后才被调用,因此输出结果不少都是10
虽然启动一个携程代价很小,可是也不能无限制地建立携程,不然致使cpu占用太高
func main() { var limit = make(chan int, 3) for _, id := range ids { go func() { limit <- 1 worker(id) <-limit }() } for {} }
当限制并发数的时候,若是有大量写通道,会形成通道阻塞过长
func main() { select { case id <- 1: println("success") case <- time.After(3 * time.Second): println("timeout") } }
例如在tcp编程中,一个 goroutine 用来读,一个 goroutine 用来写,读写 goroutine 间用通道传递消息
func main() { listen, _ := net.Listen("tcp4", ":9001") defer listen.Close() for { conn, _ := listen.Accept() ch := make(chan string, 10) go read(conn, ch) go write(conn, ch) } } func write(conn net.Conn, ch <-chan string) { for msg := range ch { _, err := conn.Write([]byte(msg)) if err != nil { break } } } func read(conn net.Conn, ch chan<- string) { for { msg := make([]byte, 1024) n, err := conn.Read(msg) if err != nil { break } ch <- string(msg[:n]) } }