goroutine 分析 协程的调度和执行顺序 并发写 run in the same address space 内存地址 闭包 存在两种并发 确定性 非确定性的 Go 的协程和通道理所当然的支持确定性的并发方式(
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 = 26 func main() {
const GOMAXPROCS = 1
runtime.GOMAXPROCS(GOMAXPROCS)
var wg sync.WaitGroup
wg.Add(2 * N)
for i := 0; 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 分析 协程的调度和执行顺序 并发写 run in the same address space 内存地址 闭包 存在两种并发 确定性 非确定性的 Go 的协程和通道理所当然的支持确定性的并发方式(的更多相关文章
- goroutine 分析 协程的调度和执行顺序 并发写
package main import ( "fmt" "runtime" "sync" ) const N = 26 func main( ...
- python接口自动化(二十二)--unittest执行顺序隐藏的坑(详解)
简介 大多数的初学者在使用 unittest 框架时候,不清楚用例的执行顺序到底是怎样的.对测试类里面的类和方法分不清楚,不知道什么时候执行,什么时候不执行.虽然或许通过代码实现了,也是稀里糊涂的一知 ...
- {MySQL的逻辑查询语句的执行顺序}一 SELECT语句关键字的定义顺序 二 SELECT语句关键字的执行顺序 三 准备表和数据 四 准备SQL逻辑查询测试语句 五 执行顺序分析
MySQL的逻辑查询语句的执行顺序 阅读目录 一 SELECT语句关键字的定义顺序 二 SELECT语句关键字的执行顺序 三 准备表和数据 四 准备SQL逻辑查询测试语句 五 执行顺序分析 一 SEL ...
- 45、SQL逻辑查询语句执行顺序
一 SELECT语句关键字的定义顺序 SELECT DISTINCT <select_list> FROM <left_table> <join_type> JOI ...
- 第四篇:记录相关操作 SQL逻辑查询语句执行顺序
http://www.cnblogs.com/linhaifeng/articles/7372774.html 一 SELECT语句关键字的定义顺序 SELECT DISTINCT <selec ...
- 9、SQL逻辑查询语句执行顺序
本篇导航: SELECT语句关键字的定义顺序 SELECT语句关键字的执行顺序 准备表和数据 准备SQL逻辑查询测试语句 执行顺序分析 一.SELECT语句关键字的定义顺序 SELECT DISTIN ...
- SQL逻辑查询语句执行顺序 需要重新整理
一.SQL语句定义顺序 1 2 3 4 5 6 7 8 9 10 SELECT DISTINCT <select_list> FROM <left_table> <joi ...
- mysql SQL 逻辑查询语句和执行顺序
关键字的执行优先级(重点) fromwheregroup byhavingselectdistinctorder bylimit 先创建两个表 CREATE TABLE table1 ( custom ...
- sql逻辑查询语句的执行顺序
SELECT语句关键字的定义顺序 SELECT DISTINCT <select_list> FROM <left_table> <join_type> JOIN ...
随机推荐
- Android 安全研究 hook 神器frida学习(一)
在进行安卓安全研究时,hook技术是不可或缺的,常用的有Xposed:Java层的HOOK框架,由于要修改Zgote进程,需要Root,体验过Xposed,整个过程还是很繁琐的,并且无法hook,na ...
- web基础知识,
# web基础 网上冲浪 surfing the Internet weibo.com 域名,主机名,微博服务器的地址名 当用户在地址栏输入一个URL(uniform resource,locator ...
- vuejs2.0使用Sortable.js实现的拖拽功能( 转)
文章目录 简介 实现效果 html主要代码 css代码 js代码 简介 在使用vue1.x之前的版本的时候,页面中的拖拽功能,我在项目中是直接用的jquery ui中的sortable.js,只是 ...
- 手写系列:call、apply、bind、函数柯里化
少废话,show my code call 原理都在注释里了 // 不覆盖原生call方法,起个别名叫myCall,接收this上下文context和参数params Function.prototy ...
- Python错误重试方法
前言 Tenacity是一个 Apache 2.0授权的通用重试库,用 Python 编写,用于简化向几乎所有内容添加重试行为的任务.它起源于一个重新尝试的分支,可惜这个分支已经不复存在了. 使用Te ...
- 最全Java面试题(一)
一.基础篇 1.1 java基础 面向对象的特征:继承.封装和多态 final, finally, finalize 的区别 final用于声明属性.方法和类,分别表示属性不可变.方法不可覆盖.类不可 ...
- js概念和ECMAScript
概念 就是一门浏览器客户端的脚本语言 运行在客户端浏览器中的,每一个浏览器都有JavaScript的解析引擎. 脚本语言,不需要编译,直接就可以被浏览器解析执行. 好处: 可以增强一些用户的 ...
- [Skill] git下载助手
缘由 某些时候需要下载git项目中的某个独立的文件或者文件夹下的内容,克隆全部速度过于慢 搜索下发现有开源的实现,特此记录下 方案 国内不便于fq 源码 服务地址 国外原作者 源码 服务地址 使用方法 ...
- 杭电OJ----1097:一个难题(c++)
问题描述 lcy给feng5166,lwg,JGShining和Ignatius带来了一个难题:给了a和b,如何知道a ^ b.每个人都反对这个BT问题,所以lcy使问题比开始容易. 这个难题描述了: ...
- 解锁Renderbus客户端使用新技巧----快速渲染效果图篇
度娘说,效果图最基本的要求就是:应该符合事物的本身尺寸,不能为了美观而使用效果把相关模型的尺寸变动,那样的效果图不但不能起到表现设计的作用,反而成为影响设计的一个因素.可见高效渲染效果图是都是当下我们 ...