协程的特点

1.该任务的业务代码主动要求切换,即主动让出执行权限

2.发生了IO,导致执行阻塞(使用channel让协程阻塞)

与线程本质的不同

C#、java中我们执行多个线程,是通过时间片切换来进行的,要知道进行切换,程序需要保存上下文等信息,是比较消耗性能的

GO语言中的协程,没有上面这种切换,一定是通过协程主动放出权限,不是被动的。

例如:

C# 中创建两个线程

可以看到1和2是交替执行的

Go语言中用协程实现一下

runtime.GOMAXPROCS(1)

这个结果就是 执行了1 在执行2

上述两种方式来进行协程的切换

1.该任务的业务代码主动要求切换,即主动让出执行权限

2.通过channel进行阻塞

执行的结果一样

后面又继续执行1了

总结:http://www.infoq.com/cn/articles/knowledge-behind-goroutine

补充:2018/09/10 Go语言中的goroutine

一、Goroutine的并行

package main

import (
  "fmt"
  "runtime"
  "sync"
)
func main() {
  runtime.GOMAXPROCS()
  var wg sync.WaitGroup
  wg.Add()
  fmt.Println("Start Goroutines")   go func() {
    defer wg.Done()
    for i:=;i<=;i++{
      fmt.Print("")
    }
  }()   go func() {
    defer wg.Done()
    for i:=;i<=;i++ {
fmt.Print("")
    }
  }()   go func() {
    defer wg.Done()
    for i:=;i<=;i++ {
   fmt.Print("")
    }
  }()   fmt.Println("等待执行结束")
  wg.Wait()
}

运行结果

Start Goroutines

等待执行结束

(1)runtime.GOMAXPROCS(1) 的作用是什么?

runtime包的GOMAXPROCS 函数。这个函数允许程序更改调度器可以使用的逻辑处理器的数量。如果不想在代码里做这个调用,也可以通过修改和这个函数名字一样的环境变量的值来更改逻辑处理器的数量。

(2)为什么先输出3后输出1和2?

如下有详细的讲解 https://studygolang.com/topics/5157?fr=sidebar ,不过经过各种实验发现这个执行顺序其实是变的,貌似纠结这个顺序也没有什么意思。

(3)WaitGroup 是什么意思?

WaitGroup 是一个计数信号量,可以用来记录并维护运行的goroutine。如果WaitGroup的值大于0,Wait 方法就会阻塞。为了减小WaitGroup 的值并最终释放main 函数,使用defer 声明在函数退出时

调用Done 方法。(defer 有点像C#当中的fianlly)

补充:调度算法中,如果多个goroutine中某个执行过长,此时会将其停止让给其他goroutine继续执行,待到其他都执行完成,在将其进行执行。如下图:G4和G5就进行了切换

如下代码可以验证上面的问题:

package main

import (
"fmt"
"runtime"
"sync"
) var wg sync.WaitGroup func main() {
runtime.GOMAXPROCS() wg.Add() fmt.Println("Create Goroutines")
go printPrime("A")
go printPrime("B") fmt.Println("Waiting To Finish")
wg.Wait() fmt.Println("Terminating Program")
} func printPrime(prefix string) {
defer wg.Done() next:
for outer := ; outer < ; outer++ {
for inner := ; inner < outer; inner++ {
if outer%inner == {
continue next
}
}
fmt.Printf("%s:%d\n", prefix, outer)
}
fmt.Println("Completed", prefix)
}

printPrime 这个函数作用是查找显示 5000 以内的素数值,这是一个比较耗时的程序。

运行结果:数字比较多,不打印了,但是可以看到结果是A和B两个协程之间的切换。

上面的代码都是设置GOMAXPROCS为1的情况,给每个可用的核心分配一个逻辑处理器

runtime.GOMAXPROCS(runtime.NumCPU())

这样运行第一个程序的结果如下

Start Goroutines
等待执行结束133333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333332111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222

可以看到1、2、3没有什么规律。

二、Goroutine的竞争

什么竞争状态:如果两个或者多个goroutine 在没有互相同步的情况下,访问某个共享的资源,并试图同时读和写这个资源,就处于相互竞争的状态,这种情况被称作竞争状态(race candition)

竞争状态是比较容易出现问题的地方,所以对一个共享资源的读和写操作必须是原子化的,换句话说,同一时刻只能有一个goroutine 对共享资源进行读和写操作。

package main

import (
"fmt"
"runtime"
"sync"
) var (
counter int
wg sync.WaitGroup
) func main() {
wg.Add() go incCounter()
go incCounter() wg.Wait()
fmt.Println("Final Counter:", counter)
} func incCounter(id int) {
defer wg.Done()
for count := ; count < ; count++ {
value := counter
runtime.Gosched()
value++
counter = value
}
}

运行结果:

Final Counter: 

变量 counter 会进行4 次读和写操作,每个goroutine 执行两次。但是,程序终止时,counter变量的值为2,这是因为两个协程之间产生了竞争,对同一个counter进行读写,下面这个图很好的诠释了为什么最后counter是2

补充:runtime.Gosched()用于让出CPU时间片。

三、使用Goroutine的锁

一种修正代码、消除竞争状态的办法是,使用Go 语言提供的锁机制,来锁住共享资源,

从而保证goroutine 的同步状态。Go 语言提供了传统的同步goroutine 的机制,就是对共享资源加锁。

1、原子函数

使用atomic包来提供对数值类型的安全访问。

package main

import (
"fmt"
"runtime"
"sync"
"sync/atomic"
) var (
counter int64
wg sync.WaitGroup
) func main() {
wg.Add() go incCounter()
go incCounter() wg.Wait() fmt.Println("Final Counter:", counter)
} func incCounter(id int) {
defer wg.Done()
for count := ; count < ; count++ {
atomic.AddInt64(&counter, )
runtime.Gosched()
}
}

运行结果

Final Counter: 

atmoic 包的AddInt64 函数。这个函数会同步整型值的加法,方法是强制同一时刻只能有一个goroutine 运行并完成这个加法操作。当goroutine 试图去调用任何原子函数时,这些goroutine 都会自动根据所引用的变量做同步处理。

另外两个有用的原子函数是LoadInt64 和StoreInt64。这两个函数提供了一种安全地读和写一个整型值的方式。

package main

import (
"fmt"
"sync"
"sync/atomic"
"time"
) var (
shutdown int64
wg sync.WaitGroup
) func main() {
wg.Add() go doWork("A")
go doWork("B") time.Sleep( * time.Second) fmt.Println("Shutdown Now")
atomic.StoreInt64(&shutdown, )
wg.Wait()
} func doWork(name string) {
defer wg.Done() for {
fmt.Printf("Doing %s Work\n", name)
time.Sleep( * time.Millisecond) // Do we need to shutdown.
if atomic.LoadInt64(&shutdown) == {
fmt.Printf("Shutting %s Down\n", name)
break
}
}
}

主协程main函数执行了1秒,然后将shutdown中的值设置为1,A和B的协程中通过读取shutdown是否等于1来判断是否结束协程。

运行结果:

Doing A Work
Doing B Work
Doing A Work
Doing B Work
Doing A Work
Doing B Work
Doing A Work
Doing B Work
Shutdown Now
Shutting A Down
Shutting B Down

2、互斥锁

另一种同步访问共享资源的方式是使用互斥锁(mutex)。互斥锁这个名字来自互斥(mutualexclusion)的概念。互斥锁用于在代码上创建一个临界区,保证同一时间只有一个goroutine 可以执行这个临界区代码

package main

import (
"fmt"
"runtime"
"sync"
) var (
counter int
wg sync.WaitGroup
mutex sync.Mutex
) func main() {
wg.Add() go incCounter()
go incCounter()
go incCounter() wg.Wait()
fmt.Printf("Final Counter: %d\n", counter)
} func incCounter(id int) {
defer wg.Done() for count := ; count < ; count++ {
mutex.Lock()
{
value := counter
runtime.Gosched()
value++
counter = value
}
mutex.Unlock()
}
}

运行结果:

Final Counter: 

如上面的代码,最终的结果还是6.

mutex.lock he mutex.Unlock之间的代码,同一时刻只允许一个goroutine进入,所以保证数据的正确性。

补充对于 runtime.Gosched()的理解,有两个问题 1. 当执行了这句话,貌似让出了执行,那么它后面的语句还能执行么

Gosched可以参考下面的链接体会

https://nanxiao.me/go-runtime-gosched-introduction/

Go语言协程的更多相关文章

  1. 一个“蝇量级” C 语言协程库

    协程(coroutine)顾名思义就是“协作的例程”(co-operative routines).跟具有操作系统概念的线程不一样,协程是在用户空间利用程序语言的语法语义就能实现逻辑上类似多任务的编程 ...

  2. go语言协程安全map

    前言: 在go语言中 map 是很重要的数据结构.Map 是一种无序的键值对的集合.Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值.问题来了,这么安逸的 数据结构 ...

  3. Go语言协程并发---条件变量

    package main import ( "fmt" "sync" "time" ) func main() { //要监听的变量 bit ...

  4. Go语言协程并发---读写锁sync.RWMutex

    package main import ( "fmt" "sync" "time" ) /* 读写锁 多路只读 一路只写 读写互斥 */ / ...

  5. Go语言协程并发---互斥锁sync.Mutex

    package main import ( "fmt" "sync" "time" ) /* mt.Lock() 抢锁 一次只能被一个协程锁 ...

  6. Go语言协程并发---等待组sync.WaitGroup

    package main import ( "fmt" "sync" "time" ) /*等待组API介绍*/ func main071( ...

  7. Go语言协程并发---管道信号量应用

    package main import ( "fmt" "math" "strconv" "time" ) /* ·10 ...

  8. Go语言协程并发---生产者消费者实例

    package main import ( "fmt" "strconv" "time" ) /* 改进生产者消费者模型 ·生产者每秒生产一 ...

  9. Go语言协程并发---原子操作

    package main import ( "fmt" "sync/atomic" ) /* 用原子来替换锁,其主要原因是: 原子操作由底层硬件支持,而锁则由操 ...

随机推荐

  1. iOS----------developerDiskImage

    真机测试时提示Could not find Developer Disk Image.这该怎么办???? 这是由于真机系统过高或者过低,Xcode中没有匹配的配置包文件,我们可以通过这个路径进入配置包 ...

  2. wc基础功能

    第一次作业 项目地址 https://gitee.com/xxlznb/WordCount PSP WordCount 预估耗时(分钟) 实际耗时 计划 20 30 预估任务需要时间 20 30 开发 ...

  3. Python第七天 函数 函数参数 函数里的变量 函数返回值 多类型传值 函数递归调用 匿名函数 内置函数

    Python第七天   函数  函数参数   函数里的变量   函数返回值  多类型传值     函数递归调用   匿名函数   内置函数 目录 Pycharm使用技巧(转载) Python第一天   ...

  4. Python第六天 类型转换

    Python第六天   类型转换 目录 Pycharm使用技巧(转载) Python第一天  安装  shell  文件 Python第二天  变量  运算符与表达式  input()与raw_inp ...

  5. lnmp环境里安装mssql及mssql的php扩展

    小活中用到mssql,于是在自己lnmp环境中安装各mssql数据库 步骤如下: 源码编译安装 (1)下载freetds-stable-0.91源码:http://download.csdn.net/ ...

  6. Swift4.0 从相册中获取图片和拍照

    第一步 添加协议 UIImagePickerControllerDelegate,UINavigationControllerDelegate   第二步 添加选择方式 let sexActionSh ...

  7. IOS判断NSArray是否为空

    场景描述:判断一个集合是否为空,如果不为空执行A,如果为空执行B Java实现方法 public void exec(){ List<String> list = this.getCont ...

  8. #018 C语言刷题 素数问题

    今天做题学会了一个求素数的方法 总分 13 孪生素数 相差为2的两个素数称为孪生素数.例如,3与5,41与43等都是孪生素数.设计程序求出指定区间上的所有孪生素数对.区间上限和下限由键盘获取. 程序运 ...

  9. 浏览器仿EXCEL表格插件 版本更新 - 智表ZCELL产品V1.3发布

    智表(zcell)是一款浏览器仿excel表格jquery插件.智表可以为你提供excel般的智能体验,支持双击编辑.设置公式.设置显示小数精度.下拉框.自定义单元格.复制粘贴.不连续选定.合并单元格 ...

  10. Django REST framework基础:视图和路由

    DRF中的Request 在Django REST Framework中内置的Request类扩展了Django中的Request类,实现了很多方便的功能--如请求数据解析和认证等. 比如,区别于Dj ...