goroutine 分析 协程的调度和执行顺序 并发写
package main import (
"fmt"
"runtime"
"sync"
) const N = 26 func main() {
const GOMAXPROCS = 1
runtime.GOMAXPROCS(GOMAXPROCS)
var wg sync.WaitGroup
wg.Add(N)
for i := 0; i < N; i++ {
go func(i int) {
defer wg.Done()
fmt.Println(i)
}(i)
}
wg.Wait()
}
25
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main import (
"fmt"
"runtime"
"sync"
) const N = 26 func main() {
const GOMAXPROCS = 1
runtime.GOMAXPROCS(GOMAXPROCS)
var wg sync.WaitGroup
wg.Add(N)
for i := 0; i < N; i++ {
go func() {
defer wg.Done()
fmt.Println(i)
}()
}
wg.Wait()
}
26
26
26
26
26
26
26
26
26
26
26
26
26
26
26
26
26
26
26
26
26
26
26
26
26
26
package main import (
"fmt"
"runtime"
"sync"
) const N = 26 func main() {
const GOMAXPROCS = 1
runtime.GOMAXPROCS(GOMAXPROCS)
var wg sync.WaitGroup
wg.Add(4)
for i := 0; i < N; i++ {
go func(i int) {
defer wg.Done()
fmt.Println(i)
}(i)
}
wg.Wait()
}
25
0
1
2
package main
import "fmt"
func main() {
for i:=0; i<10; i++ {
go func() {
fmt.Println(i)
}()
}
}
无任何打印
package main import (
"fmt"
"runtime"
"sync"
) const N = func main() {
const GOMAXPROCS =
runtime.GOMAXPROCS(GOMAXPROCS)
var wg sync.WaitGroup
wg.Add( * N)
for i := ; i < N; i++ {
go func(i int) {
defer wg.Done()
fmt.Printf("%c", 'a'+i)
}(i) go func(i int) {
defer wg.Done()
fmt.Printf("%c", 'A'+i)
}(i)
}
go func() {}()
wg.Wait()
}
通过无缓冲的通道阻塞来实现控制goroutine的执行顺序
unbuffered channel
无缓冲的通道
在接收前没有能力保存任何值的通道
要求发送goroutine和接收goroutine同时准备好,才能完成发送和接收的操作
如果两个goroutine没有同时准备好,通道会导致先执行发送或接收操作的goroutine阻塞等待
这种对通道进行发送和接收的交互行为本身就是同步的
其中任意一个操作都无法离开另一个操作单独存在
Go基础系列:指定goroutine的执行顺序 - 骏马金龙 - 博客园 https://www.cnblogs.com/f-ck-need-u/p/9994652.html
package main import (
"fmt"
"time"
) func A(a, b chan struct{}) {
<-a
fmt.Println("A()!")
close(b)
} func B(a, b chan struct{}) {
<-a
fmt.Println("B()!")
close(b)
}
func C(a chan struct{}) {
<-a
fmt.Println("C()!")
} func main() {
/*
unbuffered channel
无缓冲的通道
在接收前没有能力保存任何值的通道
要求发送goroutine和接收goroutine同时准备好,才能完成发送和接收的操作
如果两个goroutine没有同时准备好,通道会导致先执行发送或接收操作的goroutine阻塞等待
这种对通道进行发送和接收的交互行为本身就是同步的
其中任意一个操作都无法离开另一个操作单独存在
*/
x := make(chan struct{})
y := make(chan struct{})
z := make(chan struct{})
go C(z)
go B(y, z)
go C(z)
go A(x, y)
go C(z)
close(x)
// 给打印留时间
time.Sleep(3 * time.Second)
}
A()!
B()!
C()!
C()!
C()!
goroutine并发写
package main import (
"math/rand"
"sync"
) const N = 10 func main() {
m := make(map[int]int)
wg := &sync.WaitGroup{}
wg.Add(N)
for i := 0; i < N; i++ {
go func() {
defer wg.Done()
m[rand.Int()] = rand.Int()
}()
}
wg.Wait()
println(len(m))
}
当N相对大时,比如10e4报错
加锁
同步访问共享资源的方式之一
使用互斥锁mutex
互斥锁概念来自互斥(mutual excusion)概念
互斥锁用于在代码上创建一个临界区,保证同一时间只有一个goroutine可以执行这个临界区代码
《Go 语言实战》
package main import (
"math/rand"
"sync"
) const N = 100000 func main() {
m := make(map[int]int)
wg := &sync.WaitGroup{}
var mutex sync.Mutex
wg.Add(N)
for i := 0; i < N; i++ {
go func() {
defer wg.Done()
mutex.Lock()
m[rand.Int()] = rand.Int()
mutex.Unlock()
}()
}
wg.Wait()
println(len(m))
}
用无缓冲的通道来模拟2个goroutine间的网球比赛
package main import (
"fmt"
"math/rand"
"sync"
"time"
) // 用来等待程序结束
var wg sync.WaitGroup func init() {
rand.Seed(time.Now().UnixNano())
}
func main() {
// 创建一个无缓冲的通道
court := make(chan int)
// 计数加2,表示要等待2个goroutine
wg.Add(2)
// 启动2个选手
go player("A", court)
go player("B", court)
// 发球
court <- 1
// 等待游戏结束
wg.Wait()
} // player模拟一个选手在打网球
func player(name string, court chan int) {
// 在函数退出时调用Done来通知main函数工作已经完成
defer wg.Done() for {
// 等待球被击打过来
ball, ok := <-court
if !ok {
// 如果通道关闭,我们就赢了
fmt.Printf("Player %s Won\n", name)
return
}
// 选随机数,然后用这个数来判断我们是否丢球
n := rand.Intn(100)
if n%13 == 0 {
fmt.Printf("Player %s Missed\n", name)
close(court)
return
}
// 显示击球数,并将击球数加1
fmt.Printf("Player %s Hit %d\n", name, ball)
ball++ // 将球打向对手
court <- ball }
}
Player B Hit 1
Player A Hit 2
Player B Hit 3
Player A Hit 4
Player B Hit 5
Player A Hit 6
Player B Hit 7
Player A Hit 8
Player B Missed
Player A Won
接力比赛
package main import (
"fmt"
"sync"
"time"
) var wg sync.WaitGroup func main() {
// 创建一个无缓冲的通道
baton := make(chan int) // 为最后一位跑步者将计数加1
wg.Add(1) // 第一位跑步者持有接力棒
go Runner(baton) // 开始比赛
baton <- 1 // 等待比赛结束
wg.Wait()
} // Runner 模拟接力比赛中的一位跑步者 func Runner(baton chan int) {
var newRunner int
// 等待接力棒
runner := <-baton // 开始绕着跑道跑步
fmt.Printf("Runner %d Running With Baton\n", runner) // 创建下一位跑步者
if runner != 4 {
newRunner = runner + 1
fmt.Printf("Runner %d To The Line\n", newRunner)
go Runner(baton)
} // 围绕跑道跑
time.Sleep(100 * time.Millisecond) // 比赛结束了吗?
if runner == 4 {
fmt.Printf("Runner %d Finished, Race over\n", runner)
wg.Done()
return
} // 将接力棒交给下一位跑步者
fmt.Printf("Runner %d Exchange With Runner %d\n", runner, newRunner) baton <- newRunner }
Runner 1 Running With Baton
Runner 2 To The Line
Runner 1 Exchange With Runner 2
Runner 2 Running With Baton
Runner 3 To The Line
Runner 2 Exchange With Runner 3
Runner 3 Running With Baton
Runner 4 To The Line
Runner 3 Exchange With Runner 4
Runner 4 Running With Baton
Runner 4 Finished, Race over
package main import (
"fmt"
"math/rand"
"sync"
"time"
) const (
numberGorutines = 4 // 要使用的goroutine的数量
taskLoad = 10 // 要处理的工作的数量
) var wg sync.WaitGroup // init初始化包,Go语言运行时会在其他代码执行之前
// 优先执行这个函数
func init() {
// 初始化随机数种子
rand.Seed(time.Now().Unix())
} func main() {
// 创建一个有缓冲的通道来管理工作
tasks := make(chan string, taskLoad) // 启动goroutine来处理工作
wg.Add(numberGorutines)
for gr := 1; gr <= numberGorutines; gr++ {
go worker(tasks, gr)
} // 增加一组要完成的工作
for post := 1; post <= taskLoad; post++ {
tasks <- fmt.Sprintf("Task : %d", post)
} // 当所有工作都处理完时关闭通道
// 以便所有goroutine退出
close(tasks) // 等待所有工作完成
wg.Wait() } // worker作为goroutine启动来处理
// 从有缓冲的通道传入的工作 func worker(tasks chan string, worker int) {
// 通知函数已经返回
defer wg.Done() for {
// 等待分配工作
task, ok := <-tasks
if !ok {
// 这意味着通道已经空了,并且已被关闭
fmt.Printf("Worker: %d : Shutting Down\n", worker)
return
}
// 显示我们开始工作了
fmt.Printf("Worker: %d : Started %s\n", worker, task) // 随机等一段时间来模拟工作
sleep := rand.Int63n(100)
time.Sleep(time.Duration(sleep) * time.Millisecond) // 显示我们完成了工作
fmt.Printf("Worker: %d : Completed %s \n", worker, task)
}
}
Worker: 4 : Started Task : 1
Worker: 1 : Started Task : 2
Worker: 2 : Started Task : 3
Worker: 3 : Started Task : 4
Worker: 2 : Completed Task : 3
Worker: 2 : Started Task : 5
Worker: 2 : Completed Task : 5
Worker: 2 : Started Task : 6
Worker: 2 : Completed Task : 6
Worker: 2 : Started Task : 7
Worker: 4 : Completed Task : 1
Worker: 4 : Started Task : 8
Worker: 2 : Completed Task : 7
Worker: 2 : Started Task : 9
Worker: 4 : Completed Task : 8
Worker: 4 : Started Task : 10
Worker: 1 : Completed Task : 2
Worker: 1 : Shutting Down
Worker: 2 : Completed Task : 9
Worker: 2 : Shutting Down
Worker: 3 : Completed Task : 4
Worker: 3 : Shutting Down
Worker: 4 : Completed Task : 10
Worker: 4 : Shutting Down
能够从已经关闭的通道接收数据这一点非常重要,因为这允许通道关闭后
依旧能够取出其中缓冲的全部值,而不会有数据丢失。
从一个已经关闭且没有数据的通道里获取数据,总会立刻返回,并返回一个通道类型的零值。
goroutine
https://tour.golang.org/concurrency/1
Goroutines run in the same address space, so access to shared memory must be synchronized. The sync package provides useful primitives, although you won't need them much in Go as there are other primitives. (See the next slide.)
https://golang.org/doc/faq#closures_and_goroutines
What happens with closures running as goroutines?
Some confusion may arise when using closures with concurrency. Consider the following program:
func main() {
done := make(chan bool)
values := []string{"a", "b", "c"}
for _, v := range values {
go func() {
fmt.Println(v)
done <- true
}()
}
// wait for all goroutines to complete before exiting
for _ = range values {
<-done
}
}
One might mistakenly expect to see a, b, c as the output. What you'll probably see instead is c, c, c. This is because each iteration of the loop uses the same instance of the variable v, so each closure shares that single variable. When the closure runs, it prints the value of v at the time fmt.Println is executed, but v may have been modified since the goroutine was launched. To help detect this and other problems before they happen, run go vet.
To bind the current value of v to each closure as it is launched, one must modify the inner loop to create a new variable each iteration. One way is to pass the variable as an argument to the closure:
for _, v := range values {
go func(u string) {
fmt.Println(u)
done <- true
}(v)
}
In this example, the value of v is passed as an argument to the anonymous function. That value is then accessible inside the function as the variable u.
Even easier is just to create a new variable, using a declaration style that may seem odd but works fine in Go:
for _, v := range values {
v := v // create a new 'v'.
go func() {
fmt.Println(v)
done <- true
}()
}
This behavior of the language, not defining a new variable for each iteration, may have been a mistake in retrospect. It may be addressed in a later version but, for compatibility, cannot change in Go version 1.
闭包
https://github.com/unknwon/the-way-to-go_ZH_CN/blob/master/eBook/14.1.md
存在两种并发方式:确定性的(明确定义排序)和非确定性的(加锁/互斥从而未定义排序)。Go 的协程和通道理所当然的支持确定性的并发方式(例如通道具有一个 sender 和一个 receiver)。
goroutine 分析 协程的调度和执行顺序 并发写的更多相关文章
- goroutine 分析 协程的调度和执行顺序 并发写 run in the same address space 内存地址 闭包 存在两种并发 确定性 非确定性的 Go 的协程和通道理所当然的支持确定性的并发方式(
package main import ( "fmt" "runtime" "sync" ) const N = 26 func main( ...
- Goroutine(协程)为何能处理大并发?
简单来说:协程十分轻量,可以在一个进程中执行有数以十万计的协程,依旧保持高性能. 进程.线程.协程的关系和区别: 进程拥有自己独立的堆和栈,既不共享堆,亦不共享栈,进程由操作系统调度. 线程拥有自己独 ...
- python并发编程之进程、线程、协程的调度原理(六)
进程.线程和协程的调度和运行原理总结. 系列文章 python并发编程之threading线程(一) python并发编程之multiprocessing进程(二) python并发编程之asynci ...
- qemu核心机制分析-协程coroutine
关于协程coroutine前面的文章已经介绍过了,本文总结对qemu中coroutine机制的分析,qemu 协程coroutine基于:setcontext函数族以及函数间跳转函数siglongjm ...
- 多道技术 进程 线程 协程 GIL锁 同步异步 高并发的解决方案 生产者消费者模型
本文基本内容 多道技术 进程 线程 协程 并发 多线程 多进程 线程池 进程池 GIL锁 互斥锁 网络IO 同步 异步等 实现高并发的几种方式 协程:单线程实现并发 一 多道技术 产生背景 所有程序串 ...
- 浅谈Go语言的Goroutine和协程
0x00.前言 前面写了一篇初识Go语言和大家一起学习了Go语言的巨大潜力.语言简史.杀手锏特性等,感兴趣的读者可以回顾一下. 今天来学习Go语言的Goroutine机制,这也可能是Go语言最为吸引人 ...
- Unity在协程内部停止协程自身后代码执行问题
当在协程内部停止自身后,后面的代码块还会继续执行,直到遇到yield语句才会终止. 经测试:停止协程,意味着就是停止yield,所以在停止协程后,yield之后的语句也就不会执行了. 代码如下: us ...
- python并发编程之asyncio协程(三)
协程实现了在单线程下的并发,每个协程共享线程的几乎所有的资源,除了协程自己私有的上下文栈:协程的切换属于程序级别的切换,对于操作系统来说是无感知的,因此切换速度更快.开销更小.效率更高,在有多IO操作 ...
- 分析Tornado的协程实现
转自:http://www.binss.me/blog/analyse-the-implement-of-coroutine-in-tornado/ 什么是协程 以下是Wiki的定义: Corouti ...
随机推荐
- POJ 3743 LL’s cake(圆+PSLG)
题意是给你一块在原点半径为10的圆,然后告诉你一条直线在圆弧上的极角,相当于用这条直线把这个圆分成两半,然后一共是n条直线切圆,就好比切蛋糕,问你其中最大一块的面积是多少. 如果我们将圆弧转化成直线边 ...
- spark性能调优03-shuffle调优
1.开启map端输出文件的合并机制 1.1 为什么要开启map端输出文件的合并机制 默认情况下,map端的每个task会为reduce端的每个task生成一个输出文件,reduce段的每个task拉取 ...
- vue-sticky组件详解
sticky简介 sticky的本意是粘的,粘性的,使用其进行的布局被称为粘性布局. sticky是position属性新推出的值,属于CSS3的新特性,常用与实现吸附效果. 设置了sticky布局的 ...
- 通过编写串口助手工具学习MFC过程--(十一)弹出模态型对话框
通过编写串口助手工具学习MFC过程 因为以前也做过几次MFC的编程,每次都是项目完成时,MFC基本操作清楚了,但是过好长时间不再接触MFC的项目,再次做MFC的项目时,又要从头开始熟悉.这次通过做一个 ...
- zprofiler工具
转自:zprofiler三板斧解决cpu占用率过高问题 此工具为阿里自产的profiler工具,在其他文章中看到有用此工具进行性能问题定位的.在此转载文章学习一下. 上周五碰到了一个线上机器cpu占用 ...
- 发布一本用 GitBook 编辑的书
在上一篇的文章里,我们已经写好了一本名叫 erdong-first-book 的书,但是在本地浏览很不方便,我们希望放到网络上,可以随时.方便的访问这个书籍.这个需求可以使用多种方式来实现,比如第一种 ...
- 如何增强Linux和Unix服务器系统安全性
众所周知,网络安全是一个非常重要的课题,而 Linux 和 unix 又是一种服务器上运行最广告的操作系统,下面本文将就加强一些适当的配置来防止一些安全问题的发生,以增强Linux/Unix服务器系统 ...
- switch语句能否作用在byte,long,string上
switch是java中的多分支结构.在switch(expr)中,expr只能是一个整数表达式,或者是枚举常量,整数表达式可以是int基本类型也可以是Integer包装类型,由于byte,short ...
- linux中文件权限格式与chmod命令以及用户和用户组的管理
简单了解一下linux中的文件权限格式与chmod命令 chmod命令:改变文件或者目录的权限 格式:chmod [参数] [<权限范围><符号><权限代码>] - ...
- k3 cloud中如何把一个账套中的单据部署到另一个账套中
打开bos,依次点击->解决方案->部署包管理 填写部署包名称并点击下一步 选择需要部署的单据并点击下一步 确定后点击下一步: 点击完成 找到部署路径会成一个部署包: 部署:打开部署包安装 ...