go语言的设计初衷除了在不影响程序性能的情况下减少复杂度,另一个目的是在当今互联网大量运算下,如何让程序的并发性能和代码可读性达到极致。go语言的并发关键词 "go"

  1. go dosomething() //走,兄弟我们搞点事情

案例一:并发编程

  1. func say(s string) {
  2. fmt.Printf("%s say\n", s)
  3. }
  4. func main() {
  5. go say("lisi")
  6. say("zhangsan")
  7. }

执行结果

  1. zhangsan say

上面的案例执行了2次say方法,但只有zhangsan执行成功了。原因是因为lisi是开了一个goroutine去执行,还没执行完但此时的main函数已经退出了。

案例二:并发编程

lisi估计是有点害羞,说话语速比较慢,因此我们要等lisi一下,抛开串行执行和sleep外我们用一个消息管道类通知,这里我们就要zhangsan和lisi一起说

  1. func say(s string, c chan int) {
  2. fmt.Printf("%s say\n", s)
  3. c <- 1 //在消息管道里传1,代表我已经说过了
  4. }
  5. func main() {
  6. c := make(chan int)
  7. go say("lisi", c)
  8. go say("zhangsan", c)
  9. v1, v2 := <-c, <-c
  10. fmt.Printf("lisi:%d , zhangsan:%d\n", v1, v2)
  11. }

执行结果如下,当然也有可能lisi say 在zhangsan say的前面,等于1代表他俩都说过话了

  1. zhangsan say
  2. lisi say
  3. lisi:1 , zhangsan:1

过程分解

1、创建一个无缓冲的channel

2、异步执行 go say("lisi", c)

3、异步执行 go say("zhangsan", c)

4、假设zhangsan先执行,那么zhangsan的1先放入管道c,如果这时候正好lisi在执行,不好意思管道c只有1个长度放不下了。此时lisi: c<- 1阻塞

5、v1 := <- c 执行,zhangsan的值1从管道里拿出来了。

6、lisi执行 c <- 1

7、v2 := <- c执行,lisi的值1也从管道里拿出来了

8、执行fmt.Printf

并发编程就这么用的,不过大家发现问题没有,过程分解步骤4有阻塞,同一时刻5和7也是阻塞的(等待管道里拿值迟迟拿不到)

适当改版一下,如下:

  1. func say(s string, c chan int) {
  2. fmt.Printf("%s say\n", s)
  3. c <- 1 //在消息管道里传1,代表我已经说过了
  4. }
  5. func main() {
  6. c := make(chan int, 2) //改动点,管道长度设成了2
  7. go say("lisi", c)
  8. go say("zhangsan", c)
  9. v1, v2 := <-c, <-c
  10. fmt.Printf("lisi:%d , zhangsan:%d\n", v1, v2)
  11. }

这时候的过程分解

1、创建一个缓冲为2的channel

2、异步执行 go say("lisi", c)

3、异步执行 go say("zhangsan", c)

4、假设zhangsan先执行,那么zhangsan的1先放入管道c,如果这时候正好lisi在执行,lisi的1也放入管道c

5、v1 := <- c 执行,zhangsan的值1从管道里拿出来了。

6、v2 := <- c执行,lisi的值1也从管道里拿出来了

7、执行fmt.Printf

理论上来说应该是少了一步,实际情况可能会更好一些,因为步骤4没有阻塞(也就是zhangsan和lisi的值1可以同时放进去)。

步骤5和6虽然有阻塞(这里的阻塞跟c#里的await是一个意思),但是管道c一旦有值会立马拿出来,等v1和v2都有值了然后执行fmt.Printf

又有问题了!

  1. 如果say方法有返回值怎么办? 如下代码案例说明
  1. func say(s string) int {
  2. fmt.Printf("%s say\n", s)
  3. return 1
  4. }
  5. func main() {
  6. msg:= go say("lisi", c) //PS:这里会报错syntax error: unexpected go, expecting expression
  7. }
  1. 这个chan只能传int或者string 如果我的返回只是一个struct结构体(实体)怎么办?
  2. 如果say方法是别人写的,他的参数没有chan管道我又想并发执行怎么办?

还是看代码吧

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. //学生结构体(实体)
  6. type Stu struct {
  7. Name string
  8. Age int
  9. }
  10. func say(name string) Stu {
  11. fmt.Printf("%s say\n", name)
  12. stu := Stu{Name: name, Age: 18}
  13. return stu
  14. }
  15. func main() {
  16. c := make(chan int)
  17. go func() {
  18. stu := say("lisi") //返回一个学生实体
  19. fmt.Printf("我叫%s,年龄%d\n", stu.Name, stu.Age)
  20. c <- 1 //信号位表示调用完毕
  21. }()
  22. fmt.Println("go func")
  23. <-c
  24. fmt.Println("end")
  25. }

执行结果:

go func

lisi say

我叫lisi,年龄18

end

错误示范:死锁

  1. func say(s string, c chan int) {
  2. fmt.Printf("%s say\n", s)
  3. //c <- 1 这里本来应该给c管道传值的,结果没传
  4. }
  5. func main() {
  6. c := make(chan int)
  7. go say("lisi", c)
  8. v1 := <-c //这里会一直阻塞,导致死锁
  9. fmt.Printf("lisi:%d\n", v1) //前面死锁,这里无法输出
  10. }

执行报错内容:

fatal error: all goroutines are asleep - deadlock!

goroutine简析:

goroutine也叫协程是一种轻量级别用户空间线程,不受操作系统的调度,所以需要用户自行调度(一般是加锁和信道),协程能做的事情进程和线程同样能做。进程和线程的切换主要依赖于时间片的控制,而协程的切换则主要依赖于自身,这样的好处是避免了无意义的调度,由此可以提高性能,但也因此,程序员必须自己承担调度的责任

什么是协程:from百科

协程与子例程一样,协程(coroutine)也是一种程序组件。相对子例程而言,协程更为一般和灵活,但在实践中使用没有子例程那样广泛。协程源自 Simula 和 Modula-2 语言,但也有其他语言支持

ps:子例程是某个主程序的一部分代码

goroutine可以看作是协程的go语言实现,它是语言原生支持的,相对于一般由库实现协程的方式,goroutine更加强大,它的调度一定程度上是由go运行时(runtime)管理。其好处之一是,当某goroutine发生阻塞时(例如同步IO操作等),会自动出让CPU给其它goroutine。

后面会单独的在介绍进程、线程、协程之前的关系,也可以参考以下几篇文章

  1. 进程、线程、轻量级进程、协程和go中的Goroutine 那些事儿
  2. golang的goroutine是如何实现的?

channel 简析

channel是Go语言在语言级别提供的goroutine间的通信方式。我们可以使用channel在两个或 多个goroutine之间传递消息。channel是进程内的通信方式,因此通过channel传递对象的过程和调用函数时的参数传递行为比较一致,比如也可以传递指针等。如果需要跨进程通信,我们建议用 分布式系统的方法来解决,比如使用Socket或者HTTP等通信协议。Go语言对于网络方面也有非常完善的支持。 channel是类型相关的。也就是说,一个channel只能传递一种类型的值,这个类型需要在声明channel时指定。如果对Unix管道有所了解的话,就不难理解channel,可以将其认为是一种类 型安全的管道。

关于channel有必要详细了解下。可以参考

golang的channel使用

重点来了,goroutine号称是轻松开上万个并发

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. )
  6. var sum int = 0
  7. func todo(i int, c chan int) {
  8. //c <- 1 //执行一次放一个值1
  9. c <- i //把i的值放进去
  10. }
  11. func getSum(count int, c chan int, ce chan int) {
  12. for i := 0; i <= count; i++ {
  13. sum += <-c
  14. // k, isopen := <-c
  15. // if !isopen {
  16. // fmt.Printf("channel is close")
  17. // break
  18. // } else {
  19. // fmt.Printf("sum:%d,k:%d\n", sum, k)
  20. // sum += k
  21. // }
  22. }
  23. ce <- 1
  24. }
  25. func main() {
  26. count := 100000 //10W个goroutine
  27. c := make(chan int, count) //有缓冲channel
  28. ce := make(chan int) //计算getSum信号量
  29. //开始计时
  30. begin := time.Now()
  31. fmt.Println("开始时间:", begin)
  32. for i := 0; i <= count; i++ {
  33. go todo(i, c)
  34. }
  35. //再开一个goroutine去计算channel里的值求Sum
  36. go getSum(count, c, ce)
  37. <-ce //这里是getSum方法执行结束信号量
  38. end := time.Now()
  39. fmt.Println("结束时间:", end, time.Since(begin))
  40. fmt.Println(sum)
  41. }

硬件信息

环境:THINKPAD L460、WIN7x64、8G内存、i5-6200U 2.3GHz 双核4线程

语言:LiteIDE X33、golang 1.9.2

多次执行结果:38.5ms - 51ms之间

再次改版下

把 c := make(chan int, count) 改为 c := make(chan int) 改成无缓冲

  1. c := make(chan int) //重点,这里改成无缓冲的

多次执行结果:304-325ms之间

结论:明显无缓冲区耗时多了接近300ms,这部分时间实际是channel读取阻塞的时间,因此在大量并发的情况下channel的缓冲区大小会直接影响程序的性能,这也是前面提到需要用户自行调度的原因之一!!!

顺便来一发.net core 的并发代码实验,和上面goroutine同样的机器和环境

  1. class Program
  2. {
  3. private static readonly object obj = new object();
  4. static void Main(string[] args)
  5. {
  6. DateTime begin = DateTime.Now;
  7. long sum = 0;
  8. Parallel.For(1, 100001, (i) =>
  9. {
  10. lock (obj)
  11. {
  12. sum += i;
  13. }
  14. });
  15. TimeSpan ts = DateTime.Now - begin;
  16. Console.WriteLine($"{sum},耗时:{ts.TotalMilliseconds}ms");
  17. Console.ReadLine();
  18. }
  19. }

运行结果 : 大约在90-120ms左右,虽然数值上差了2倍左右,其实差别并不是很大,也没有直接的可比性,因为线程和协程并不是一个数量级别,上面goroutine用到了channel通道,net core 用的lock锁,因此仅供参考。总体看来.net core的性能整体还是蛮高的



PS:题外话 其实c#里也有协程"fiber",网上资料比较少了解不多。

golang并发编程goroutine+channel(一)的更多相关文章

  1. Golang并发编程——goroutine、channel、sync

    并发与并行 并发和并行是有区别的,并发不等于并行. 并发 两个或多个事件在同一时间不同时间间隔发生.对应在Go中,就是指多个 goroutine 在单个CPU上的交替运行. 并行 两个或者多个事件在同 ...

  2. Golang - 并发编程

    目录 Golang - 并发编程 1. 并行和并发 2. go语言并发优势 3. goroutine是什么 4. 创建goroutine 5. runtime包 6. channel是什么 7. ch ...

  3. golang并发编程

    golang并发编程 引子 golang提供了goroutine快速实现并发编程,在实际环境中,如果goroutine中的代码要消耗大量资源时(CPU.内存.带宽等),我们就需要对程序限速,以防止go ...

  4. Golang并发编程优势与核心goroutine及注意细节

    Go语言为并发编程而内置的上层API基于CSP(communication sequential processes,顺序通信进程)模型.这就意味着显式锁都是可以避免的,比如资源竞争,比如多个进程同时 ...

  5. golang并发编程的两种限速方法

    引子 golang提供了goroutine快速实现并发编程,在实际环境中,如果goroutine中的代码要消耗大量资源时(CPU.内存.带宽等),我们就需要对程序限速,以防止goroutine将资源耗 ...

  6. Golang并发编程基础

    硬件 内存 作为并发编程一个基础硬件知识储备,首先要说的就是内存了,总的来说在绝大多数情况下把内存的并发增删改查模型搞清楚了其他的基本上也是异曲同工之妙. 内存芯片--即我们所知道的内存颗粒,是一堆M ...

  7. Golang并发编程进程通信channel了解及简单使用

    概念及作用 channel是一个数据类型,用于实现同步,用于两个协程之间交换数据.goroutine奉行通过通信来共享内存,而不是共享内存来通信.引用类型channel是CSP模式的具体实现,用于多个 ...

  8. Golang并发编程有缓冲通道和无缓冲通道(channel)

    无缓冲通道 是指在接收前没有能力保存任何值得通道.这种类型的通道要求发送goroutine和接收goroutine同时准备好,才能完成发送和接收操作.如果两个goroutine没有同时准备好,通道会导 ...

  9. go/wiki/MutexOrChannel Golang并发:选channel还是选锁?

    https://mp.weixin.qq.com/s/JcED2qgJEj8LaBckVZBhDA https://github.com/golang/go/wiki/MutexOrChannel M ...

随机推荐

  1. 实现类似AOP的封装和配置

    这是张孝祥老师Java进阶讲解中最后一个视频,就是实现类似spring中的AOP的封装和配置,特别特别小型的一个框架雏形,但是spring中的核心思想还是体现出来了,真的厉害,张老师!!! 一.重点知 ...

  2. 【贪心 思维题】[USACO13MAR]扑克牌型Poker Hands

    看似区间数据结构的一道题 题目描述 Bessie and her friends are playing a unique version of poker involving a deck with ...

  3. (14)zabbix Simple checks基本检测

    1. 开始 Simple checks通常用来检查远程未安装代理或者客户端的服务 使用simple checks,被监控客户端无需安装zabbix agent客户端,zabbix server直接使用 ...

  4. Python基础:字典(dict)与集合(set)

    查找场景下与列表的性能对比 字典与集合之所以高效的原因是:内部结构都是一张哈希表. 平均情况下插入.查找和删除的时间复杂度为 O(1). 假设有数量100,000的产品列表: import time ...

  5. ZOJ 3469 区间DP Food Delivery

    题解 #include <iostream> #include <cstdio> #include <cstring> #include <algorithm ...

  6. C#上位机开发(二)—— Hello,World

    上一篇大致了解了一下单片机实际项目开发中上位机开发部分的内容以及VS下载与安装,按照编程惯例,接下来就是“Hello,World!” 1.新建C#项目工程 首先选择新建Windows窗体应用(.NET ...

  7. expdp / impdp 用法详解(Oracle)

    一 .关于expdp和impdp     使用EXPDP和IMPDP时应该注意的事项: EXP和IMP是客户端工具程序,它们既可以在客户端使用,也可以在服务端使用. EXPDP和IMPDP是服务端的工 ...

  8. luogu1879 [USACO06NOV]玉米田Corn Fields

    学习位运算 #include <iostream> #include <cstdio> using namespace std; int n, m, dp[15][4105], ...

  9. Codeforces Round #438 by Sberbank and Barcelona Bootcamp (Div. 1 + Div. 2 combine

    最近只想喊666,因为我是真得菜,大晚上到网吧打代码还是很不错的嘛 A. Bark to Unlock time limit per test 2 seconds memory limit per t ...

  10. Axure:从单一评价方式到用户自由选择

    导读: 亲,还记得淘宝对货物的评价方式吗?还记得对快递哥的评价方式吗? 1,经典五星评:                                                         ...