Golang 并发
- 并行 指的是在同一时刻 有多条指令在多个CPU处理器上同时执行 2个任务2个窗口需要硬件支持
- 并发是指在同一时刻 只能有一条指令 单多个进程指令快速轮换执行 2个队伍1个窗口 要求提升软件能力
Golang 并发优势
- go 从底层就支持并发
- 简化了并发程序的编写方法
Goroutine 是什么
- 它是go 并发设计的核心
- goroutine就是协程,它比线程更小,十几个goroutine 在底层可能就是五六个线程
- go 语言内部实现了goroutine的内存共享,执行goroutine只需极少的栈内存(大概4-5KB)
创建goroutine
- 只需要在语句前添加go 关键字即可
package main
import (
"fmt"
"time"
)
//测试协程
//循环打印内容
func newTask() {
i := 0
for {
i++
fmt.Printf("new goroutine\:i=%d\n", i)
time.Sleep(1 \* time.Second)
}
}
//main()相当于是主协程
func main() {
//启动子协程
go newTask()
i := 0
for {
i++
fmt.Printf("main goroutine\:i=%d\n", i)
time.Sleep(1 \* time.Second)
}
}
- 开发人员无需了解任何执行细节,调度器会自动将其安排到合适的系统线程上运行
- 如果主协程推出,所有子进程协程都会退出
package main
import (
"fmt"
"time"
)
//main()相当于是主协程
func main() {
//匿名子协程
go func() {
i := 0
for {
i++
fmt.Println("子协程 i=", i)
time.Sleep(1 \* time.Second)
}
}()
i := 0
for {
i++
fmt.Println("主协程 i=", i)
time.Sleep(1 \* time.Second)
//主协程第二次后退出
if i == 2 {
break
}
}
}
- 程序没任何输出,也不报错
package main
import (
"fmt"
"time"
)
//main()相当于是主协程
func main() {
//匿名子协程
go func() {
i := 0
for {
i++
fmt.Println("子协程 i=", i)
time.Sleep(1 \* time.Second)
}
}()
}
runtime 包
- runtime.Gosched(): 用于让出CPU时间片,调度器重新安排任务调度,还是有几率分配到它的
package main
import (
"fmt"
"runtime"
)
func main() {
//匿名子协程
go func(s string) {
for i := 0; i < 2; i++ {
fmt.Println(s)
}
}("world")
//主协程
for i := 0; i < 2; i++ {
runtime.Gosched()
fmt.Println("hello")
}
}
- runtime.Goexit() 立即终止当前协程
package main
import (
"fmt"
"time"
"runtime"
)
func main() {
//匿名子协程
go func() {
defer fmt.Println("A.defer")
//匿名函数
func() {
defer fmt.Println("B.defer")
//此时只有defer执行
runtime.Goexit()
fmt.Println("B")
}()
fmt.Println("A")
}()
for {
time.Sleep(time.Second)
}
}
- runtime.GOMAXPROCS(): 设置并行计算的CPU核数,返回之前的值
package main
import (
"runtime"
"fmt"
)
func main() {
n := runtime.GOMAXPROCS(3)
fmt.Println("n=%d\n",n)
//循环执行2个
for{
go fmt.Print(0)
fmt.Print(1)
}
}
channel
- goroutine运行在相同的地址空间,因此访问共享内存必须做好同步,处理好线程安全
- goroutine奉行通过通信来共享内存,而不是共享内存通信
- channel 是一个引用类型 用于多个goroutine 通讯 其内部实现了同步,确保并发安全
Channel 基本使用
- Channel 可以用内置make() 创建
- 定义一个channel 也需要定义发送到channel值类型
make(chan 类型) // 无缓冲通道
make(chan 类型, 容量) // 有缓冲通道 - 当capacity=0 时, channel 是无缓冲阻塞读写的,当capacity>0, channel 有缓冲,是非阻塞的,直到写满capacity个元素才阻塞写入
- channel 通过操作符<-来接受和发送数据,语法为:
channel <- value //发送value到channel
<-channel //接受通道数据,丢弃
x := <-channel //取值并赋给x
x, ok := <-channel //ok 是检查通道是否关闭或者是否为空 - channel 基本使用
package main
import "fmt"
func main() {
//创建存放int类型的通道
c := make(chan int)
//子协程
go func() {
defer fmt.Println("子协程结束")
fmt.Println("子协程正在运行...")
//将666发送到通道c
c <- 666
}()
//若已取出数据,下面再取会报错
//<-c
//主协程取数据
//从c中取数据
num := <-c
fmt.Println("num = ", num)
fmt.Println("主协程结束")
}
无缓冲的channel
- 无缓冲的通道是指在接收前没有能力保存任何值的通道
- 无缓冲通道,有可能阻塞
- 发送者-> (通道(有可能数据阻塞)) -> 接受者
package main
import (
"fmt"
"time"
)
func main() {
//创建无缓冲通道
c := make(chan int, 0)
//长度和容量
fmt.Printf("len(c)=%d, cap(c)=%d\n", len(c), cap(c))
//子协程存数据
go func() {
defer fmt.Println("子协程结束")
//向通道添加数据
for i := 0; i < 3; i++ {
c <- i
fmt.Printf("子协程正在运行[%d]:len(c)=%d,cap(c)=%d\n", i, len(c), cap(c))
}
}()
time.Sleep(2 * time.Second)
//主协程取数据
for i := 0; i < 3; i++ {
num := <-c
fmt.Println("num=", num)
}
fmt.Println("主协程结束")
}
有缓冲channel
-
有缓冲的通道是一种在被接受前能存储一个或着多个值的通道 发送者->(通道(数据),(数据)(...))->接受者 在创建时修改容量即可
//创建有缓存的通道
c :=make(chan int, 3)
关闭close 通过内置close()关闭channel
package main
import "fmt"
func main() {
//创建通道
c := make(chan int)
//子协程存数据
go func() {
for i := 0; i < 5; i++ {
c <- i
}
//子协程close
close(c)
}()
//主协程取数据
for {
if data, ok := <-c; ok {
fmt.Println(data)
} else {
break
}
}
fmt.Println("Finshed")
}
# 可以遍历
for data := range c{
fnt.Println(data)
}
单方向channel
-
默认下 通道是双向的,也就是既可以往里面发送数据也可以接收数据
-
go 可以定义单方向通道,也就是只发送数据或者只接收数据
var ch1 chan int //正常的
var ch2 chan <- float64 //单向的 只用于写float64的数据
var ch3 <-chan int //单向的, 只用于读取int 数据文章来源:https://www.toymoban.com/news/detail-776870.html -
可以将channel 隐式转换为单向队列,只收或只发,不能讲单向channel 转为普通channel文章来源地址https://www.toymoban.com/news/detail-776870.html
//1. 将c准换为只写的通道
var send <- chan int =c
//2. 将c转为只读的通道
var recv <- chan int =c
//往send里面写数据
send < -1
//从recv读数据
<-recv
- 单方向的channel 场景 模拟生产者,消费者
package main
import "fmt"
//生产者,只写
func producter(out chan<- int) {
//关闭资源
defer close(out)
for i := 0; i < 5; i++ {
out <- i
}
}
//消费者,只读
func consumer(in <-chan int) {
for num := range in {
fmt.Println(num)
}
}
func main() {
//创建通道
c := make(chan int)
//生产者运行,向管道c存数据
go producter(c)
//消费者运行
consumer(c)
fmt.Println("done")
}
定时器
- Timer 定时 时间到了响应一次
package main
import (
"time"
"fmt"
)
func main() {
//1.基本使用
//创建定时器
//2秒后,定时器会将一个时间类型值,保存向自己的c
//timer1 := time.NewTimer(2 * time.Second)
////打印当前时间
//t1 := time.Now()
//fmt.Printf("t1:%v\n", t1)
////从管道中取出C打印
//t2 := <-timer1.C
//fmt.Printf("t2:%v\n", t2)
//2.Timer只响应一次
//timer2 := time.NewTimer(time.Second)
//for {
// <-timer2.C
// fmt.Println("时间到")
//}
//3.通过Timer实现延时的功能
////(1)睡眠
//time.Sleep(2*time.Second)
//fmt.Println("2秒时间到")
////(2)通过定时器
//timer3 := time.NewTimer(2 * time.Second)
//<-timer3.C
//fmt.Println("2秒时间到")
////(3)After()
//<-time.After(2 * time.Second)
//fmt.Println("2秒时间到")
//4.停止定时器
//timer4 := time.NewTimer(3 * time.Second)
////子协程
//go func() {
// <-timer4.C
// fmt.Println("定时器器时间到,可以打印了")
//}()
//stop := timer4.Stop()
//if stop {
// fmt.Println("timer4已关闭")
//}
//5.重置定时器
timer5 := time.NewTimer(3 * time.Second)
//定时器改为1秒
timer5.Reset(1 * time.Second)
fmt.Println(time.Now())
fmt.Println(<-timer5.C)
for {
}
}
* 响应多次
package main
import (
"time"
"fmt"
)
func main() {
//创建定时器,间隔1秒
ticker := time.NewTicker(time.Second)
i := 0
//子协程
go func() {
for {
<-ticker.C
fmt.Println(<-ticker.C)
i++
fmt.Println("i=", i)
//停止定时器
if i == 5 {
ticker.Stop()
}
}
}()
//死循环
for {
}
select 用法
- go 语言提供了select 关键字,可以监听channel 上的数据流动
- 语法与switch 类似,区别时select 要求每个case 语句必须是一个IO操作
select {
case <-chan1:
// 如果chan1成功读到数据,则进行该case处理语句
case chan2 <- 1:
// 如果成功向chan2写入数据,则进行该case处理语句
default:
// 如果上面都没有成功,则进入default处理流程
}
```go
package main
import (
"fmt"
)
func main() {
//创建数据通道
int_chan := make(chan int, 1)
string_chan := make(chan string, 1)
//创建2个子协程,写数据
go func() {
//time.Sleep(2 * time.Second)
int_chan <- 1
}()
go func() {
string_chan <- "hello"
}()
//如果都能匹配到,则随机选择一个去跑
select {
case value := <-int_chan:
fmt.Println("intValue:", value)
case value := <-string_chan:
fmt.Println("strValue:", value)
}
fmt.Println("finish")
}
协程同步锁
- go 中channel 实现了同步,确保并发安全,同时也提供了锁的操作方式
- go 中sync包提供了锁的相关支持
- Mutex 以加锁的方式解决并发安全问题
package main
import (
"time"
"fmt"
"sync"
)
//账户
type Account struct {
money int
flag sync.Mutex
}
//模拟银行检测
func (a *Account)Check() {
time.Sleep(time.Second)
}
//设置账户余额
func (a *Account)SetAccount(n int) {
a.money +=n
}
//查询账户余额
func (a *Account)GetAccount() int{
return a.money
}
//买东西1
func (a *Account)Buy1(n int){
a.flag.Lock()
if a.money>n{
//银行检测
a.Check()
a.money -=n
}
a.flag.Unlock()
}
//买东西2
func (a *Account)Buy2(n int){
a.flag.Lock()
if a.money>n{
//银行检测
a.Check()
a.money -=n
}
a.flag.Unlock()
}
func main() {
var account Account
//设置账户余额
account.SetAccount(10)
//2个子协程买东西
go account.Buy1(6)
go account.Buy2(5)
time.Sleep(2 * time.Second)
fmt.Println(account.GetAccount())
}
- sync.WaitGroup 用来等待一组协程结束,需要设置等待个数,每个子协程结束后要调用Done() 然后在主进程Wait() 即可
package main
import (
"fmt"
)
func main() {
//创建通道
ch := make(chan int)
//count表示活动的协程个数
count := 2
go func() {
fmt.Println("子协程1")
//子协程1执行完成,给通道发送信号
ch <-1
}()
go func() {
fmt.Println("子协程2")
ch <-1
}()
//time.Sleep(time.Second)
//从ch中不断读数据
for range ch{
count --
if count == 0{
close(ch)
}
}
}
- go提供了解决方案sync.WaitGroup
- Add() 添加计数
- Done() 操作结束时调用,计数减1
- Wait() 主函数调用,等待所有操作结束
到了这里,关于golang 并发的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!