一、协程
golang中的并发是函数独立运行的能力。Goroutines是并发运行的函数。Golang提供了Goroutines作为并发处理操作的一种方式。
创建一个协程非常简单,就是在一个任务函数前面添加一个go关键字
go task()
举例:
1、只有一个协程,即主协程
package main
import (
"fmt"
"time"
)
/*
golang 协程
*/
func showMeg(msg string){
for i := 0; i < 5; i++ {
fmt.Printf("msg:%v\n", msg)
time.Sleep(time.Millisecond*100)
}
}
func main() {
showMeg("java") //加了go关键字,启动了一条协程
showMeg("golang")
fmt.Println("end...")
}
运行结果
原因:只有一个协程,所以先执行showMsg(java),再执行showMsg(go),再输出end
2、有两个协程
1)先go
package main
import (
"fmt"
"time"
)
/*
golang 协程
*/
func showMeg(msg string){
for i := 0; i < 5; i++ {
fmt.Printf("msg:%v\n", msg)
time.Sleep(time.Millisecond*100)
}
}
func main() {
go showMeg("java") //加了go关键字,启动了一条协程
showMeg("golang")
fmt.Println("end...")
}
运行结果
原因:
showMsg(java)开启了一个协程,main函数有一个主协程,所以一共有两个协程。showMsg(java)开启的协程负责打印java,后者负责打印GO和end...二者交替执行。
2)后 Go
package main
import (
"fmt"
"time"
)
/*
golang 协程
*/
func showMeg(msg string){
for i := 0; i < 5; i++ {
fmt.Printf("msg:%v\n", msg)
time.Sleep(time.Millisecond*100)
}
}
func main() {
showMeg("java")
go showMeg("golang")加了go关键字,启动了一条协程
fmt.Println("end...")
}
运行结果
showMsg(Go)开启了一个线程,负责打印GO,主协程打印java和end...,但是由于开启showMsg(Go)协程时,java已经打印完,所以主协程在打印完end...之后就结束了。主协程一旦结束,就会自动结束其他的协程
3、有三个协程
package main
import (
"fmt"
"time"
)
/*
golang 协程
*/
func showMeg(msg string){
for i := 0; i < 5; i++ {
fmt.Printf("msg:%v\n", msg)
time.Sleep(time.Millisecond*100)
}
}
func main() {
go showMeg("java") //协程一
go showMeg("golang")//协程二
fmt.Println("end...") //协程三-主协程
}
运行结果
end...或者end... msg:java;
发现运行结果不一样且没怎么打印原因:
三个协程同时运行。如果协程三,即主协程退出了,就会自动结束协程一二
所以打印结果会出现上面这种情况。
解决办法:另主协程等待一段时间再执行:
package main
import (
"fmt"
"time"
)
/*
golang 协程
*/
func showMeg(msg string){
for i := 0; i < 5; i++ {
fmt.Printf("msg:%v\n", msg)
time.Sleep(time.Millisecond*100)
}
}
func main() {
go showMeg("java") //协程一
go showMeg("golang")//协程二
time.Sleep(time.Millisecond*2000) //另主协程等待一段时间再执行
fmt.Println("end...") //协程三-主协程
}
运行结果
二、channel
channel(通道)用于在协程之间共享数据。通道分为两种:有缓冲通道、无缓冲通道。无缓冲通道用于执行协程之间的同步通信,而缓冲通道用于执行异步通信。无缓冲通道保证在发送和接受发生的瞬间执行两个协程之间的交换,缓冲通道没有这样的保证
1、语法
Unbuffered := make(chan int)
buffered := make(chan int,10)
使用内置函数make创建无缓冲和缓冲通道,make的第一个参数需要关键字chan,然后是通道允许交换的数据类型。如果是有缓存通道需要指定缓冲区大小,即第二个参数
2、向通道传递
channel<- value //把value传递到通道
value:=<-channel //从通道中获取信息存入value
3、示例
package main
import (
"fmt"
"time"
"math/rand"
)
/*
golang chnnel通道
*/
var values = make(chan int)
func send(){
rand.Seed(time.Now().UnixNano())
value:=rand.Intn(10)
fmt.Printf("send:%v\n", value)
channel<- value
}
func main() {
//从通道接收值
defer close(values) //延迟退出,只有main主函数退出的时候,通道才会关闭
go send()
fmt.Println("wait...")
value:=<-channel
fmt.Printf("receive:%v\n", value)
fmt.Println("end...")
}
/*
wait...
send:9
receive:9
end...
*/
一共有两个协程:send()和主函数协程。send()通过通道values向主函数发送value,主函数通过通道接收
三、WaitGroup 实现同步
同步:两个协程之间互相等待
package main
import (
"fmt"
// "time"
// "math/rand"
"sync" //waitGroup在这个包下面
)
/*
golang WaitGroup实现同步
*/
var wg sync.WaitGroup //使用waitGroup
func hello(i int) {
defer wg.Done() //协程结束waitgroup数记减一
fmt.Println("hello Goroutine",i)
}
func main() {
for i := 0; i < 10; i++ {
wg.Add(1) //每执行一个协程就waitgroup数加1
//启动一个协程来执行
go hello(i)
}
//主协程
wg.Wait()
fmt.Println("end...")
}
/*
如果没有加group的执行结果 并不是0-9都打印出来了。这是因为:
一旦主协程结束了,其他的协程就会被强制终止执行
end...
hello Goroutine 5
*/
/*
解决办法:使用waitGroup来完成协程之间的同步
hello Goroutine 9
hello Goroutine 4
hello Goroutine 5
hello Goroutine 3
hello Goroutine 1
hello Goroutine 6
hello Goroutine 7
hello Goroutine 8
hello Goroutine 2
hello Goroutine 0
end...
*/
四、runtime包
runtime包里面定义了一些协程管理相关的kpi
1、runtime.Gosched()
让出CPU时间片,重新安排等待任务---当前时间片轮到我执行了,但是我不执行,我让出cpu,给其他协程
package main
import (
"fmt"
"runtime"
// "time"
// "math/rand"
// "sync" //waitGroup在这个包下面
)
/*
golang runtime
*/
func show(msg string){
for i := 0; i < 2; i++ {
fmt.Printf("msg:%v\n", msg)
}
}
func main() {
go show("java") //启动子协程
//加入runtime
for i := 0; i <2; i++ {
runtime.Gosched()//我有权力执行任务了,让给其他子协程来执行
}
fmt.Println("end....")
}
/*
没有使用runtime包时的运行结果:因为主协程运行完之后会把子协程强制关闭
end....
或者
end....
msg:java
*/
/*
使用runtime 在主协程可以执行时,不执行,让给子协程来执行。从而子协程可以执行完
有可能让cpu的第一次子协程就执行完了,那么第二次就让不出去了,只能主协程自己执行
也有可能cpu第二从让给子协程,子协程才执行完
msg:java
msg:java
end....
*/
2、runtime.Goexit()
退出当前协程
package main
import (
"fmt"
"runtime"
"time"
// "math/rand"
// "sync" //waitGroup在这个包下面
)
/*
golang runtime
*/
func show(){
for i := 0; i < 10; i++ {
fmt.Printf("i:%v\n",i)
if i>=5{
runtime.Goexit()
}
}
}
func main() {
go show()
time.Sleep(time.Second)
}
/*
i:0
i:1
i:2
i:3
i:4
i:5
*/
3、runtime.GOMAXPROCS
package main
import (
"fmt"
"runtime"
"time"
// "math/rand"
// "sync" //waitGroup在这个包下面
)
/*
golang runtime
*/
func showa(){
for i := 0; i < 10; i++ {
fmt.Printf("a:%v\n",i)
}
}
func showb(){
for i := 0; i < 10; i++ {
fmt.Printf("b:%v\n",i)
}
}
func main() {
fmt.Printf("runtime.NumCPU():%v\n", runtime.NumCPU()) //16
runtime.GOMAXPROCS(2) //设置CPU核心数,如果不设置就取最多的
go showa()
go showb()
time.Sleep(time.Second)
}
/*
如果设置最大cpu核心数是1,都是b先执行再a,或者a先b后,不会交替
b:0
b:1
b:2
b:3
b:4
b:5
b:6
b:7
b:8
b:9
a:0
a:1
a:2
a:3
a:4
a:5
a:6
a:7
a:8
a:9
*/
/*
如果cpu核心数设置为大于1的数,则会交替执行
runtime.NumCPU():16
a:0
a:1
a:2
a:3
a:4
a:5
b:0
b:1
b:2
b:3
b:4
b:5
b:6
b:7
b:8
b:9
a:6
a:7
a:8
a:9
*/
五、Mutex互斥锁实现同步
除了使用channel实现同步之外,还可以使用Mutex互斥锁实现同步
package main
import (
"fmt"
// "runtime"
"time"
// "math/rand"
"sync" //waitGroup在这个包下面
)
/*
golang mutex
*/
var i int =100
var wg sync.WaitGroup
var lock sync.Mutex
func add(){
defer wg.Done()
lock.Lock() //加锁
i+=1
fmt.Printf("i++:%v\n", i)
time.Sleep(time.Millisecond*10)
lock.Unlock() //解锁
}
func sub(){
defer wg.Done()
lock.Lock() //加锁
i-=1
fmt.Printf("i--:%v\n", i)
time.Sleep(time.Millisecond*2)
lock.Unlock() //解锁
}
func main() {
for i := 0; i < 100; i++ {
wg.Add(1)
go add()
wg.Add(1)
go sub()
}
wg.Wait()
fmt.Printf("end i:%v\n", i)
}
/*
没有使用协程,运行结果
i++:101
i--:100
i++:101
i--:100
i++:101
i--:100
.......
end i:100
*/
/*
使用协程 出现了不一致的结果
end i:99
i++:97
*/
/*
使用mutex进行加锁,完成对于共享资源的完整访问与更改
*/
六、channel的遍历
注意:如果写的时候不关闭通道,在发生读比写多这种情况时,会发生死锁;其他情况不会。
package main
import (
"fmt"
// "runtime"
// "time"
// "math/rand"
// "sync" //waitGroup在这个包下面
)
/*
golang channe的遍历
*/
var c=make(chan int)
func main() {
go func(){
for i := 0; i < 2; i++ {
c<-i
}
close(c)//关闭通道
}()
//方式一
for i := 0; i < 3; i++ {
r:=<-c
fmt.Printf("r:%v\n", r)
}
//方式二
for v := range c {
fmt.Printf("v:%v\n", v)
}
//方式三:
for{
v,ok:=<-c
if ok{
fmt.Printf("v:%v\n", v)
}else{
break
}
}
// r:=<-c
// fmt.Printf("r:%v\n", r)
// r=<-c
// fmt.Printf("r:%v\n", r)
// r=<-c
// fmt.Printf("r:%v\n", r)
}
七、select
package main
import (
"fmt"
// "runtime"
"time"
// "math/rand"
// "sync" //waitGroup在这个包下面
)
/*
golang select
*/
var chanInt=make(chan int,0)
var chanStr=make(chan string)
func main() {
go func(){
chanInt<-100
chanStr<-"zxx"
close(chanInt)
close(chanStr)
}()
for{
select{
case r:=<-chanInt:
fmt.Printf("chanInt:%v\n", r)
case r:=<-chanStr:
fmt.Printf("chanStr:%v\n", r)
default:
fmt.Println("default...")
}
time.Sleep(time.Second)
}
}
运行结果:
分析:读出100和zxx之后,通道里就没有东西了,所以每次读出都是默认值(如果defer close不写,即不关闭通道。那么读比写多就会造成死锁了)
八、Timer
实现一些定时的操作,内部也是通过channel来实现的
package main
import (
"fmt"
// "runtime"
"time"
// "math/rand"
// "sync" //waitGroup在这个包下面
)
/*
golang timer
*/
func main() {
//方式一
timer:=time.NewTimer(time.Second*2)//声明一个timer,等待两秒钟
fmt.Printf("time.Now():%v\n", time.Now())//读取当前的时间 2023-10-23 19:24:30.7490238
t1:=<-timer.C // 阻塞,直到计时器触发 理解:timer是个容器,我们写入计时器
//timer.C是计时器的通道,这里面是要等待时间。取出来之后,阻塞。
fmt.Printf("t1:%v\n", t1) //2023-10-23 19:24:32.7523558
//方式二:
fmt.Printf("time.Now():%v\n", time.Now())
timer1:=time.NewTimer(time.Second*2)
<-timer1.C
fmt.Printf("timer1:%v\n",timer1)
//方式三:
time.Sleep(time.Second)
//方式四:调用After
<-time.After(time.Second*2)
//方式五:stop 停止计时器
timer2:=time.NewTimer(time.Second) //等待一秒钟
go func(){
<-timer2.C
fmt.Println("func...")
}()
s:=timer2.Stop()
if s{
fmt.Println("stop...")
}
time.Sleep(time.Second*3)
fmt.Println("main end...")
/*
运行结果
stop...
main end...
原因:timer2是一个计时器。timer2.stop停止了计时器。所以有关计时器的都不会
执行了,例如匿名函数,所以不会输出func...
*/
//reset方法 更改等待时间
fmt.Println("before")
timer4:=time.NewTimer(time.Second*2)
timer4.Reset(time.Second)
<-timer4.C
fmt.Println("after")
}
九、Ticker
timer只执行一次,Ticker可以周期性执行
package main
import (
"fmt"
// "runtime"
"time"
// "math/rand"
// "sync" //waitGroup在这个包下面
)
/*
golang timer
*/
func main() {
ticker:=time.NewTicker(time.Second)
counter:=1
for _ = range ticker.C {
fmt.Println("ticker...") //每隔一秒钟执行一次
counter++
if counter>=5{
ticker.Stop() //停止计时器
break //退出函数
}
}
}
package main
import (
"fmt"
// "runtime"
"time"
// "math/rand"
// "sync" //waitGroup在这个包下面
)
/*
golang timer
*/
func main() {
ticker:=time.NewTicker(time.Second)
chanInt:=make(chan int)
//写
go func(){
for _= range ticker.C {
select{
case chanInt<-1:
case chanInt<-2:
case chanInt<-3:
}
}
}()
//读
sum:=0
for v := range chanInt {
fmt.Println("收到:",v)
sum+=v
if sum>=10{
break
}
}
}
/*
收到: 3
收到: 1
收到: 1
收到: 2
收到: 1
收到: 3
*/
十、原子变量
1、通过加锁,实现写成之间的同步
package main
import (
"fmt"
// "runtime"
"time"
// "math/rand"
"sync" //waitGroup在这个包下面
"sync/atomic" //原子操作包
)
/*
golang 原子操作
*/
var i =100
//互斥操作--定义锁 加锁之后就可以实现线程之间的同步
var lock sync.Mutex
func add(){
lock.Lock()
i++
lock.Unlock()
}
func sub(){
lock.Lock()
i--
lock.Unlock()
}
func main() {
for i := 0; i < 100; i++ {
go add()
go sub()
}
time.Sleep(time.Second*2) //如果不等一会儿,主协程就退出了,子协程就会被强制停止
fmt.Println("i:",i)
}
2、通过原子操作
package main
import (
"fmt"
// "runtime"
"time"
// "math/rand"
"sync" //waitGroup在这个包下面
"sync/atomic" //原子操作包
)
/*
golang 原子操作
*/
var i int32=100 //注意这个类型
var lock sync.Mutex
func add(){
//原子操作
atomic.AddInt32(&i,1) //atomic.AddInt32(取要使用的变量的指针,操作数)
}
func sub(){
atomic.AddInt32(&i,-1)
}
func main() {
for i := 0; i < 100; i++ {
go add()
go sub()
}
time.Sleep(time.Second*2) //如果不等一会儿,主协程就退出了,子协程就会被强制停止
fmt.Println("i:",i)
}
原子操作详解
atomic常见操作:
- 增减
- 载入 read
- 比较并交换cas
- 交换
- 存储 write
package main
import (
"fmt"
// "runtime"
// "time"
// "math/rand"
// "sync" //waitGroup在这个包下面
"sync/atomic" //原子操作包
)
/*
golang 原子操作
*/
func add_sub(){
//增减
var i int32=100
atomic.AddInt32(&i,1)
fmt.Println("i:",i) //101
atomic.AddInt32(&i,-1)
fmt.Println("i:",i) //100
var i1 int64=200
atomic.AddInt64(&i1,1)
fmt.Println("i1:",i) //101
atomic.AddInt64(&i1,-1)
fmt.Println("i1:",i) //100
}
func load_store(){
//载入和存储(读写)
var i int32=100
atomic.LoadInt32(&i) //载入 load=read 读的时候保持原子 100
fmt.Println("i:",i)
atomic.StoreInt32(&i,200) //读出 write 写的时候保持并发 200
fmt.Println("i:",i)
}
func cas(){
//cas 根基
var i int32=100
b:=atomic.CompareAndSwapInt32(&i,100,200)
fmt.Printf("b:%v\n", b) //true
fmt.Printf("i:%v\n", i) //200
/*
func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)
addr:一个指向要进行比较和交换的 int32 值的指针。
old:期望的旧值。如果 addr 中的值等于 old,则执行交换操作。
new:要设置的新值。如果 addr 中的值等于 old,则将 addr 的值设置为 new。
所以,如果有一个协程过来修改了i,则该函数返回false,表示无法将i修改为新值
*/
}
func main() {
}