Golang并发编程
go 语言的并发编程,以下是需要了解的基础知识点,也是本文主要介绍的内容. 可以对照看看这些是否已经可以熟练运用了.
-
阻塞: 阻塞是进程(也可以是线程、协程)的状态之一(新建、就绪、运行、阻塞、终止). 指的是当数据未准备就绪,这个进程(线程、协程)一直等待,这就是阻塞.
-
非阻塞: 当数据为准备就绪,该进程(线程、协程)不等待可以继续执行,这就是非阻塞.
-
同步: 在发起一个调用时,在没有得到结果之前,这个调用就不返回,这个调用过程一直在等待. 这是同步.
-
异步: 在发起调用后,就立刻返回了,这次调用过程就结束了. 等到有结果了被调用方主动通知调用者结果. 这是异步.
-
go(协程): 通过关键字 go 即可创建一个协程.
-
chan : golang 中用于并发的通道,用于协程的通信.
-
- 有缓冲通道
- 无缓冲通道
- 单向通道
-
select: golang 提供的多路复用机制.
-
close(): golang 的内置函数, 可以关闭一个通道.
-
sync: golang 标准库之一,提供了锁.
-
定时器: golang 标准库 time 提供的重要功能, 提供了定时器功能,可用于超时处理.
-
-
Timer
-
Ticker
-
协程_go
Go中的并发是函数相互独立运行的能力。Goroutines是并发运行的函数。Golang提供了Goroutines作为并发
处理操作的一种方式。
创建一个协程非常简单,就是在一个任务函数前面添加一个go关键字
//协程
/*
Golang中的并发是函数相互独立运行的能力。Goroutines是并发运行的函数。Golang提供了Goroutines作为并发
处理操作的一种方式。
创建一个协程非常简单,就是在一个任务函数前面添加一个go关键字
*/
package main
import (
"fmt"
"time"
)
func dow1() {
fmt.Println("开始下载Golang......")
time.Sleep(time.Second * 1)
fmt.Println("Golang下载完成!")
}
func dow2() {
fmt.Println("开始下载Python......")
time.Sleep(time.Second * 3)
fmt.Println("Python下载完成!")
}
func main() {
go dow1() //该函数开启协程
dow2()
}
通道_channel
Go提供了一种称为通道的机制,用于在goroutine之间共享数据。当您作为goroutine执行并发活动时,需要在goroutine之间共享资源或数据,通道充当goroutine之间的管道(管道)并提供一种机制来保证同步交换。需要在声明通道时指定数据类型。我们可以共享内置、命名、结构和引用类型的值和指针。数据在通道上传递:在任何给定时间只有一个goroutine可以访问数据项:因此按照设计不会发生数据竞争。根据数据交换的行为,有两种类型的通道:无缓冲通道和缓冲通道。无缓冲通道用于执行goroutine之间的同步通信,而缓冲通道用于执行异步通信。无缓冲通道保证在发送和接收发生的瞬间执行两个goroutine之间的交换。缓冲通道没有这样的保证。
通道由make函数创建,该函数指定chan关键字和通道的元素类型。
通道的发送和接收特性
1.对于同一个通道,发送操作之间是互库的,接收操作之间也是互库的。
2.发送操作和接收操作中对元素值的处理都是不可分割的。
3.发送操作在完全完成之前会被阻塞。接收操作也是如此。
package main
import (
"fmt"
"time"
)
//定义一个无缓冲区的通道
var Block = make(chan int)
//定义一个有缓冲区的通道
var Block_512 = make(chan int ,512)
func Sed() {
v := 30
fmt.Println("发生值 V:", v)
time.Sleep(time.Second * 2)
Block <- v //向无缓存区存存入数据
}
func test() {
i := 1
for i <= 5 {
println(i)
Block_512 <- i ////向有缓存区存存入数据
i++
}
}
func test2() {
i := 1
for i <= 5 {
v := <- Block_512 //向有缓冲区取数据
println("V:",v)
}
}
func main() {
go test()
go Sed()
go test2()
defer close(Block)
fmt.Println("等待接收:")
v := <-Block //向无缓存区取数据
fmt.Println("接收值 V:", v)
fmt.Println("END")
}
主进程等待_WaitGroup
作用于主进程等待协程执行完在执行
package main
import (
"fmt"
"sync"
)
var Wg sync.WaitGroup //定义一个 WaitGroup
func Work(i int) {
fmt.Println("Hello",i)
defer Wg.Done() //协程结束时调用WaitGroup.Done() 删除任务
}
func main() {
i := 0
for i<=10 {
Wg.Add(1) //协程开始前在WaitGroup+1
go Work(i)
i++
}
Wg.Wait() //阻塞等待直到WaitGroup进程为0 ,可以注释该行查看效果
fmt.Println("END")
}
进程调度_runtime
runtime包里面定义了一些协程管理相关的api
runtime.Goexit() 结束进程
runtime.Gosched() 让出CPU时间片,让其他进程优先执行
runtime.NumCPU() 获取CPU核心数
runtime.GOMAXPROCS() 设置程序使用CPU最大核心数
package main
import (
"fmt"
"runtime"
)
func show(s string) {
i := 1
for i <= 10 {
fmt.Println("Show:",s)
i++
}
}
func show2(s string) {
////测试runtime.Goexit() 结束进程
i := 1
for i <= 10 {
if i > 5{
runtime.Goexit() //该进程退出
}
fmt.Println("Show:",s)
i++
}
}
func test1() {
//测试runtime.Gosched() 让出CPU时间片,让其他进程优先执行
go show("Golang") //开启一个协程
go show2("Python")
runtime.Gosched() //等待其他进程执行,可以注释掉这行看效果
i := 1
for i <= 10 {
fmt.Println("Show: test1()", )
i++
}
}
func main() {
max := runtime.NumCPU() //使用runtime.NumCPU() 获取CPU核心数
fmt.Println("CPU核心数:",max)
runtime.GOMAXPROCS(2) //使用runtime.GOMAXPROCS() 设置程序使用CPU最大核心数
test1()
}
互斥锁实现同步_Mutex
除了使用channel实现同步之外,还可以使用Mutex互斥锁的方式实现同步。
package main
//互斥锁 Mutex
import (
"fmt"
"sync"
)
var lock sync.Mutex //创建Mutex 互斥锁
var Wg sync.WaitGroup //创建WaitGroup进程队列
var i int = 100
func add() {
lock.Lock() //进程开始前锁定
i++
fmt.Println("add:", i)
lock.Unlock() //进程结束后解锁
Wg.Done() //进程队列减一
}
func sub() {
lock.Lock()
i--
fmt.Println("sub:", i)
lock.Unlock()
Wg.Done() //进程队列减一
}
func main() {
for a := 1; a <= 100; a++ {
Wg.Add(1)
go add() //进程队列加一
Wg.Add(1)
go sub()
}
Wg.Wait() //等待进程队列运行完成
fmt.Println("END :", i)
}
channel遍历
package main
import "fmt"
var block = make(chan int)
func f2() {
for i := 1;i<=10;i++ {
block <- i
}
defer close(block) //写入完成后关闭通道
}
func main() {
go f2()
for i := range block{ //遍历通道
fmt.Println(i)
}
}
Ticker_周期循环执行
Timer只执行一次
Ticker可以周期
package main
import (
"fmt"
"time"
)
func main() {
tk := time.NewTicker(time.Second * 2) //创建一个Ticker,执行周期为2秒
goo := 1
for _ = range tk.C {
fmt.Println(time.Now())
fmt.Println("RUN.....")
goo++
if goo > 5{
tk.Stop() //设置停止条件
break
}
}
}
原子变量
解决并发操作数据脏读的问题
package main
import (
"sync/atomic"
"time"
)
var i int64 = 100
func add() {
//i++ //使用传统方式需上锁,否则结果不一样
atomic.AddInt64(&i,1) //使用原子变量则可以解决
}
func sub() {
//i--
atomic.AddInt64(&i,-1)
}
func main() {
for i := 1; i <= 1000;i++{
go add()
go sub()
}
time.Sleep(time.Second*2)
println(i)
}
其他原子操作
package main
import "sync/atomic"
func main() {
var i int64 = 1000
atomic.LoadInt64(&i) //读取
println(i)
atomic.StoreInt64(&i,2000) //修改
println(i)
//比较,先比较原值,在进行修改,修改成功返回true
b := atomic.CompareAndSwapInt64(&i, 2000, 4000) //旧值 新值 返回一个bool对象
println(i,b) //4000 true
}