go dosomething() //走,兄弟咱们搞点事情
func say(s string) { fmt.Printf("%s say\n", s) } func main() { go say("lisi") say("zhangsan") }
执行结果html
zhangsan say
上面的案例执行了2次say方法,但只有zhangsan执行成功了。缘由是由于lisi是开了一个goroutine去执行,还没执行完但此时的main函数已经退出了。程序员
lisi估计是有点害羞,说话语速比较慢,所以咱们要等lisi一下,抛开串行执行和sleep外咱们用一个消息管道类通知,这里咱们就要zhangsan和lisi一块儿说golang
func say(s string, c chan int) { fmt.Printf("%s say\n", s) c <- 1 //在消息管道里传1,表明我已经说过了 } func main() { c := make(chan int) go say("lisi", c) go say("zhangsan", c) v1, v2 := <-c, <-c fmt.Printf("lisi:%d , zhangsan:%d\n", v1, v2) }
执行结果以下,固然也有可能lisi say 在zhangsan say的前面,等于1表明他俩都说过话了express
zhangsan say lisi say lisi:1 , zhangsan:1
过程分解
一、建立一个无缓冲的channel
二、异步执行 go say("lisi", c)
三、异步执行 go say("zhangsan", c)
四、假设zhangsan先执行,那么zhangsan的1先放入管道c,若是这时候正好lisi在执行,很差意思管道c只有1个长度放不下了。此时lisi: c<- 1阻塞
五、v1 := <- c 执行,zhangsan的值1从管道里拿出来了。
六、lisi执行 c <- 1
七、v2 := <- c执行,lisi的值1也从管道里拿出来了
八、执行fmt.Printf编程
并发编程就这么用的,不过你们发现问题没有,过程分解步骤4有阻塞,同一时刻5和7也是阻塞的(等待管道里拿值迟迟拿不到)
适当改版一下,以下:c#
func say(s string, c chan int) { fmt.Printf("%s say\n", s) c <- 1 //在消息管道里传1,表明我已经说过了 } func main() { c := make(chan int, 2) //改动点,管道长度设成了2 go say("lisi", c) go say("zhangsan", c) v1, v2 := <-c, <-c fmt.Printf("lisi:%d , zhangsan:%d\n", v1, v2) }
这时候的过程分解
一、建立一个缓冲为2的channel
二、异步执行 go say("lisi", c)
三、异步执行 go say("zhangsan", c)
四、假设zhangsan先执行,那么zhangsan的1先放入管道c,若是这时候正好lisi在执行,lisi的1也放入管道c
五、v1 := <- c 执行,zhangsan的值1从管道里拿出来了。
六、v2 := <- c执行,lisi的值1也从管道里拿出来了
七、执行fmt.Printf安全
理论上来讲应该是少了一步,实际状况可能会更好一些,由于步骤4没有阻塞(也就是zhangsan和lisi的值1能够同时放进去)。网络
步骤5和6虽然有阻塞(这里的阻塞跟c#里的await是一个意思),可是管道c一旦有值会立马拿出来,等v1和v2都有值了而后执行fmt.Printf并发
func say(s string) int { fmt.Printf("%s say\n", s) return 1 } func main() { msg:= go say("lisi", c) //PS:这里会报错syntax error: unexpected go, expecting expression }
仍是看代码吧异步
package main import ( "fmt" ) //学生结构体(实体) type Stu struct { Name string Age int } func say(name string) Stu { fmt.Printf("%s say\n", name) stu := Stu{Name: name, Age: 18} return stu } func main() { c := make(chan int) go func() { stu := say("lisi") //返回一个学生实体 fmt.Printf("我叫%s,年龄%d\n", stu.Name, stu.Age) c <- 1 //信号位表示调用完毕 }() fmt.Println("go func") <-c fmt.Println("end") }
执行结果:
go func
lisi say
我叫lisi,年龄18
end
func say(s string, c chan int) { fmt.Printf("%s say\n", s) //c <- 1 这里原本应该给c管道传值的,结果没传 } func main() { c := make(chan int) go say("lisi", c) v1 := <-c //这里会一直阻塞,致使死锁 fmt.Printf("lisi:%d\n", v1) //前面死锁,这里没法输出 }
执行报错内容:
fatal error: all goroutines are asleep - deadlock!
goroutine也叫协程是一种轻量级别用户空间线程,不受操做系统的调度,因此须要用户自行调度(通常是加锁和信道),协程能作的事情进程和线程一样能作。进程和线程的切换主要依赖于时间片的控制,而协程的切换则主要依赖于自身,这样的好处是避免了无心义的调度,由此能够提升性能,但也所以,程序员必须本身承担调度的责任
什么是协程:from百科
协程与子例程同样,协程(coroutine)也是一种程序组件。相对子例程而言,协程更为通常和灵活,但在实践中使用没有子例程那样普遍。协程源自 Simula 和 Modula-2 语言,但也有其余语言支持ps:子例程是某个主程序的一部分代码
goroutine能够看做是协程的go语言实现,它是语言原生支持的,相对于通常由库实现协程的方式,goroutine更增强大,它的调度必定程度上是由go运行时(runtime)管理。其好处之一是,当某goroutine发生阻塞时(例如同步IO操做等),会自动出让CPU给其它goroutine。
后面会单独的在介绍进程、线程、协程以前的关系,也能够参考如下几篇文章
channel是Go语言在语言级别提供的goroutine间的通讯方式。咱们可使用channel在两个或 多个goroutine之间传递消息。channel是进程内的通讯方式,所以经过channel传递对象的过程和调用函数时的参数传递行为比较一致,好比也能够传递指针等。若是须要跨进程通讯,咱们建议用 分布式系统的方法来解决,好比使用Socket或者HTTP等通讯协议。Go语言对于网络方面也有很是完善的支持。 channel是类型相关的。也就是说,一个channel只能传递一种类型的值,这个类型须要在声明channel时指定。若是对Unix管道有所了解的话,就不难理解channel,能够将其认为是一种类 型安全的管道。
关于channel有必要详细了解下。能够参考
golang的channel使用
package main import ( "fmt" "time" ) var sum int = 0 func todo(i int, c chan int) { //c <- 1 //执行一次放一个值1 c <- i //把i的值放进去 } func getSum(count int, c chan int, ce chan int) { for i := 0; i <= count; i++ { sum += <-c // k, isopen := <-c // if !isopen { // fmt.Printf("channel is close") // break // } else { // fmt.Printf("sum:%d,k:%d\n", sum, k) // sum += k // } } ce <- 1 } func main() { count := 100000 //10W个goroutine c := make(chan int, count) //有缓冲channel ce := make(chan int) //计算getSum信号量 //开始计时 begin := time.Now() fmt.Println("开始时间:", begin) for i := 0; i <= count; i++ { go todo(i, c) } //再开一个goroutine去计算channel里的值求Sum go getSum(count, c, ce) <-ce //这里是getSum方法执行结束信号量 end := time.Now() fmt.Println("结束时间:", end, time.Since(begin)) fmt.Println(sum) }
硬件信息
环境:THINKPAD L460、WIN7x6四、8G内存、i5-6200U 2.3GHz 双核4线程
语言:LiteIDE X3三、golang 1.9.2
屡次执行结果:38.5ms - 51ms之间
把 c := make(chan int, count) 改成 c := make(chan int) 改为无缓冲
c := make(chan int) //重点,这里改为无缓冲的
屡次执行结果:304-325ms之间
class Program { private static readonly object obj = new object(); static void Main(string[] args) { DateTime begin = DateTime.Now; long sum = 0; Parallel.For(1, 100001, (i) => { lock (obj) { sum += i; } }); TimeSpan ts = DateTime.Now - begin; Console.WriteLine($"{sum},耗时:{ts.TotalMilliseconds}ms"); Console.ReadLine(); } }
运行结果 : 大约在90-120ms左右,虽然数值上差了2倍左右,其实差异并非很大,也没有直接的可比性,由于线程和协程并非一个数量级别,上面goroutine用到了channel通道,net core 用的lock锁,所以仅供参考。整体看来.net core的性能总体仍是蛮高的
PS:题外话 其实c#里也有协程"fiber",网上资料比较少了解很少。