go语⾔学习——go并发编程
⽂章⽬录
概念
Go 语⾔的并发通过 goroutine 特性完成。goroutine 类似于线程,但是可以根据需要创建多个 goroutine 并发⼯作。Go 语⾔还提供 channel 在多个 goroutine 间进⾏通信
goroutine 是⼀种⾮常轻量级的实现,可在单个进程⾥执⾏成千上万的并发任务,它是Go语⾔并发设计的核⼼。
构建go协程
使⽤普通函数构建goroutine
package main
import(
"fmt"
"time"
)
//函数定义:构建⼀个⽆限循环打印
func running(){
var times int
// 构建⼀个⽆限循环
for{
times++
fmt.Println("tick", times)
// 延时1秒
time.Sleep(time.Second)
}
}
func main(){
// 使⽤go关键字,开启⼀个协程,并发执⾏程序
go running()
// 接受命令⾏输⼊, 不做任何事情
var input string
fmt.Scanln(&input)//这⾥会等待⽤户输⼊,当⽤户输⼊后,main函数执⾏结束
//所有 goroutine 在 main() 函数结束时会⼀同结束。
}
使⽤匿名函数构建
)
func main(){
//使⽤匿名函数,不需要加函数名字
go func(){
var times int
for{
times++
fmt.Println("tick", times)
time.Sleep(time.Second)
}
}()//直接调⽤
var input string
fmt.Scanln(&input)
}
go并发与并⾏
go并发
Go语⾔实现多核多线程并发运⾏是⾮常⽅便的,下⾯举个例⼦:
package main
import(
"fmt"
)
func main(){
//开启五个goroutine
for i :=0; i <5; i++{
go AsyncFunc(i)
}
var input string
fmt.Scanln(&input)//这⾥会等待⽤户输⼊,当⽤户输⼊后,main函数执⾏结束
}
//函数加和
func AsyncFunc(index int){
sum :=0
for i :=0; i <10000000000; i++{
sum +=1
}
fmt.Printf("线程%d, sum为:%d\n", index, sum)
}
上⾯例⼦中会创建五个goroutine然后并发执⾏,顺序是不⼀定的。
go并⾏
官⽅给出的答案是,这是当前版本的 Go 编译器还不能很智能地去发现和利⽤多核的优势。虽然我们确实创建了多个 goroutine,并且从运⾏状态看这些 goroutine 也都在并⾏运⾏,但实际上所有这些 goroutine 都运⾏在同⼀个 CPU 核⼼上。
我们可以使⽤设置环境变量来控制使⽤多少个核⼼runtime.GOMAXPROCS(16) 这⾥我们说明程序可以使⽤计算机的16个核⼼,但是具体使⽤多少,要看操作系统分配
)
func main(){
cpuNum := runtime.NumCPU()//获得当前设备的cpu核⼼数
fmt.Println("cpu核⼼数:", cpuNum)
runtime.GOMAXPROCS(cpuNum)//设置需要⽤到的cpu数量
}
go并发通信
go三种并发通信⽅式
1. 使⽤共享变量(通常和锁结合使⽤)
2. 通道(消息队列),channel(go中主要⽅式)
3. 锁:互斥锁,读写锁
在⼯程上,有两种最常见的并发通信模型:共享数据和消息。
1. 使⽤共享变量+锁
package main
import(
"fmt"
"runtime"
"sync"
)
//共享变量
var counter int=0
//协程函数:加锁
func Count(lock *sync.Mutex){
lock.Lock()
counter++
fmt.Println(counter)
lock.Unlock()
}
func main(){
//创建锁对象
lock :=&sync.Mutex{}
//创建⼗个协程并执⾏
for i :=0; i <10; i++{
go Count(lock)
}
/
/在 main 函数中,使⽤ for 循环来不断检查 counter 的值(同样需要加锁)。当其值达到 10 时,说明所有 goroutine 都执⾏完毕了,这时主函数返回,程序退出
for{
lock.Lock()
c := counter
lock.Unlock()
runtime.Gosched()//让goroutin协程暂停
if c >=10{
break
}
}
}
但是我们发现,实现⼀个如此简单的功能,需要很冗长的代码。想象⼀下,在⼀个⼤的系统中具有⽆数的锁、⽆数的共享变量、⽆数的业务逻辑与错误处理分⽀,那将是⼀场噩梦。这噩梦就是众多 C/C++ 开发者正在经历的,其实 Java 和 C# 开发者也好不到哪⾥去。
2. channel通道机制(⽆缓冲通道举例)
Go语⾔既然以并发编程作为语⾔的最核⼼优势,当然不⾄于将这样的问题⽤这么⽆奈的⽅式来解决。Go语⾔提供的是另⼀种通信模型,即以消息机制⽽⾮共享内存作为通信⽅式。Go语⾔提倡使⽤通信的⽅法代替共享内存。
go和java后端开发劣势
Go语⾔提供的消息通信机制被称为 channel。
通道的特性:
Go语⾔中的通道(channel)是⼀种特殊的类型。在任何时候同时只能有⼀个 goroutine 访问通道进⾏发送和获取数据
通道其实是⼀个消息队列,总是遵循先⼊先出(First In First Out)的规则,保证收发数据的顺序。不同的goroutine从⾥⾯放数据和取数据
创建通道
ch1 :=make(chan int)// 创建⼀个整型类型的通道
ch2 :=make(chan interface{})// 创建⼀个空接⼝类型的通道, 可以存放任意格式
type Equip struct{/* ⼀些字段 */}
ch2 :=make(chan*Equip)// 创建Equip指针类型的通道, 可以存放*Equip
将数据放⼊通道
把数据往通道中发送时,如果接收⽅⼀直都没有接收,那么发送操作将持续阻塞
// 创建⼀个空接⼝通道
ch :=make(chan interface{})
// 将0放⼊通道中
ch <-0
// 将hello字符串放⼊通道中
ch <-"hello"
接收通道中的数据
注意数据放⼊通道后要有相应的接收代码,如果只把数据放⼊通道⽽没有接收,则会运⾏期间报错。也就是说所有 goroutine 中的channel 并没有形成发送和接收对应的代码。
特性:
1. 通道的收发操作在不同的两个 goroutine 间进⾏。
由于通道的数据在没有接收⽅处理时,数据发送⽅会持续阻塞,因此通道的接收必定在另外⼀个 goroutine 中进⾏。
2. 接收将持续阻塞直到发送⽅发送数据。如果接收⽅接收时,通道中没有发送⽅发送数据,接收⽅也会发⽣阻塞,直到发送⽅发送数据
为⽌。
3. 每次接收⼀个元素。通道⼀次只能接收⼀个数据元素。
接收⽅式
1. 阻塞:data := <-ch
2. ⾮阻塞:data, ok := <-ch
3. 阻塞接收任意数据忽略返回值 <-ch
4. 使⽤for range通道循环接收for data := range ch { }
举例:阻塞接收
阻塞接收
package main
import(
"fmt"
)
func main(){
// 构建⼀个通道
ch :=make(chan int)
// 开启⼀个并发匿名函数,并直接调⽤
go func(){
fmt.Println("start goroutine")
// 将数据0放⼊通道中,通过通道通知main的goroutine  ch <-0
fmt.Println("exit goroutine")
}()
fmt.Println("wait goroutine")
// 阻塞接收,等待通道中的数据。等待匿名goroutine
<-ch
fmt.Println("all done")
}
package main
import(
"fmt"
)
func printer(c chan int){
// 开始⽆限循环等待数据
for{
// 从channel中取⼀个数据
data :=<-c
// 如果该数据为0则跳出循环
if data ==0{
break
}
// 打印数据
fmt.Println(data)
}
// 数据放⼊channel,通知main已经结束循环(我搞定了!)
c <-0
}
func main(){
// 创建⼀个channel
c :=make(chan int)
//执⾏goroutine,传⼊channel
go printer(c)
//main函数将数据1-10以此放⼊channel
for i :=1; i <=10; i++{
c <- i
}
// 最后放⼀个0进⼊channel
c <-0
/
/ 等待printer结束(搞定喊我!)
<-c
}
举例:循环接收

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。