并行和并发
并行: 指在同一时刻,有多条指令在多个处理器上同时进行
并发:同一时刻只能有一条指令执行,但是多个指令被快速的轮换执行,在宏观上体现出过个进程同时执行的效果,微观上是把时间分成若干段,使得多个进程快速交替执行
goroutine(协程)--- 并发核心
执行goroutine只要极少的栈内存(4-5kb),自动伸缩,goroutine比thread更易用、高效、轻便
// 创建一个goroutine : 只要在函数调用语句前添加go 关键字,就可以创建并发执行单元--协程
func add(){
for{
fmt.Println("bbbb")
}
}
func main(){
go add()
for{
fmt.Println("xxxx")
}
// 打一部分 xxx 打一部分 bbb
}
主协程退出,其余子协程都将被关闭--有可能主协程退出太快,导致其他子协程没调用就退出
runtime包
// runtime.Gosched 短暂让出cpu的时间片,让其他协程执行,不定时后再回到此协程
func main() {
for i := 0; i < 10; i++ {
go showNumber(i)
}
runtime.Gosched()
fmt.Println("Haha")
// 1,0,9, haha
// 1,2,3,6,haha 每次运行有不同结果
// runtime.Gosched()是短暂让出cpu的时间片,让其他协程执行,不定时后再回到此协程
}
func showNumber (i int) {
fmt.Println(i)
}
// runtime.Goexit() // 退出此协程,return 是退出此函数
// runtime.GOMAXPROCS() 指定以多少核运算,不写默认单核。runtime.GOMAXPROCS(2) 指定2核cpu运算
多任务资源竞争:
func main() {
go printer("hello")
go printer("world")
for{}
}
func printer (str string) {
for _,v:= range str{
fmt.Printf("%c",v)
time.Sleep(time.Second)
}
fmt.Print("\n") // hwoelrllod 打印出来这样,每个协程用一会儿就换另外个
}
// 为了解决多任务资源竞争的问题,需要channel实现
channel数据类型(引用类型),可以用make()创建(用于多个goruntine通讯,内部实现了同步,确保并发安全)
创建一个channel,
make(chan Type) === make(chan Type, 0 ) 无缓冲的channel 读写阻塞
make(chan Type, cap) 有缓存的channel 无阻塞,满了或者空的才阻塞
channel 数据操作
channel<- value // 发送数据到管道
<-channel //管道取出数据丢弃,
x:= <-channel // 取出数据赋值给x
x,ok := <-channel // 功能同上,同时检测通道是否已经关闭或者是否为空
默认情况下,channel收发数据都是阻塞的,除非另一端准备好,同时接收,这就让gorountine同步变得更加简单,不需要显示的lock
var chans = make(chan int) // 创建一个通道channel
func main(){
go pers("hello") // 开启新协程
go pers1("world") // 开启新协程
for{} // 防止主协程退出
// world hello
}
func pers(str string){
<-chans // 通道取值,没有则阻塞此通道运行
Printer(str)
}
func pers1(str string){
Printer(str)
chans <- 666 // 通道传递值。
}
func Printer(str string){
for _, v := range str{
fmt.Printf("%c",v)
}
fmt.Print("\n")
}
channel 实现同步和数据交互
func main(){
var cha = make(chan string)
go func (){
for i:=0;i<2; i++{
fmt.Println(i)
}
cha<- "oks"
fmt.Println("子协程结束")
}()
str := <- cha
fmt.Println(str,"主协程结束")
}
无缓冲channel
func main(){
var cha = make(chan int) //创建无缓冲channel
go func (){
for i:=0;i<2; i++{
cha <- i
fmt.Println("子协程",i)
}
fmt.Println("子协程结束")
}()
time.Sleep(time.Second)
for i:=0;i<2; i++{
str := <-cha
fmt.Println("主协程",str)
}
fmt.Println("主协程结束")
/*
主协程 0
子协程 0
子协程 1
子协程结束
主协程 1
主协程结束
*/
}
有缓冲channel
func main(){
var cha = make(chan int,3) //创建有缓冲channel
go func (){
for i:=0;i<5; i++{
cha <- i
fmt.Println("子协程",i)
}
fmt.Println("子协程结束")
}()
time.Sleep(time.Second)
for i:=0;i<5; i++{
str := <-cha
fmt.Println("主协程",str)
}
fmt.Println("主协程结束")
/*
子协程 0
子协程 1
子协程 2 // 一次性存三个,然后等一秒
主协程 0
主协程 1
主协程 2 // 一次性取三个。后面的就不确定了
主协程 3
子协程 3
子协程 4
子协程结束
主协程 4
主协程结束
*/
}
关闭channel
func main(){
var cha = make(chan int)
go func (){
for i:=0 ;i <4; i++{
cha<- i
}
close(cha) // 关闭channel,能继续读数据,不能存数据
}()
time.Sleep(time.Second)
for num:=range ch{
fmt.Println("num=",num)
} // 遍历channel,关闭后自动退出遍历
for{
if v,oks := <-cha; oks ==true{ //v为值,oks为cha是否关闭,关闭为false
fmt.Println("主协程",v)
}else{break}
}
}
单向channel特点和应用
var cha1 chan int // 正常channel,双向
var cha2 chan<- float64 // 单项,只能写入
var cha3 <-chan int // 单项,只能读取int数据
双向可以隐式转换为单项channel,反之不能
ch :=make(chan int) //双向
var wirteCh chan<- int = ch // 只能写,不能读
var readCh <-chan int = ch // 只能读,不能写
eg:
func Productor(out chan<- int){ // 参数必须为输入channel
for i:=0 ;i<10; i++ {
out<- i*i // 写入
}
close(out) // 关闭channel
}
func customer(in <-chan int){ // 参数必须为输出channel
for v := range in{ // 当channel关闭后会自动退出循环
fmt.Print(v," ")
}
}
func main(){
ch := make(chan int)
go Productor(ch)
customer(ch)
/* 0 1 4 9 16 25 36 49 64 81 */
}
定时器Timer 和Ticker
Timer:一次性定时器
// Timer实现延时功能
func main(){
timer := time.NewTimer(time.Second*4) //创建一个timer,多少秒后会注入到timer的channel里面
<-timer.C // 一开始为空值,阻塞程序运行,取到值后继续执行下面
fmt.Print(timer)
}
// time.Sleep( time.Second *n) 也能延时 n秒
// <- time.After( 2 * time.Second) // 定时2s,2s后往channel写内容,返回的是一个管道
// 停止定时器 timer.Stop()
timer := time.NewTimer(3 * time.Second)
go func(){
<-timer.C
fmt.Print("xxx")
}
timer.Stop() //停止定时器 导致xxx不能打印
// 重置定时器
timer := time.NewTimer(3* time.Second)
timer.Reset(1 * time.Second) // 重新设置为1s, 返回值为bool
<-timer.C //去channel的值
Ticker:周期往channel发送一个事件,而channel接收者可以固定时间间隔从channel里面取
ticker := time.NewTicker(time.Second*2)
i :=0
for{
i++
<-ticker.C
fmt.Println(i)
if i ==5{
ticker.Stop()
break
}
}
select 监测channel上的数据流动
每个case语句里必须是一个IO操作,在一个select语句中,Go语言会按顺序从头至尾评估每一个发送和接收的语句,如果其中的任意一语句可以继续执行(即没有被阻塞),那么就从那些可以执行的语句中任意选择一条来使用。
如果没有任意一条语句可以执行(即所有的通道都被阻塞),那么有两种可能的情况:
如果给出了default语句,那么就会执行default语句,同时程序的执行会从select语句后的语句中恢复。
如果没有default语句,那么select语句将被阻塞,直到至少有一个通信可以进行下去。
func main(){
// 超时打印
ch :=make(chan int)
quit :=make(chan bool)
go func (){
for{
select{
case num := <-ch:
fmt.Println(num)
case <-time.After(5*time.Second): //5秒后添加事件到time的channel事件
quit <- true
fmt.Print("超时quit\n")
}
}
}()
<-quit
fmt.Println("主协程退出")
}
select 实现斐波拉切数列
func main() {
// select 实现斐波拉切数列
ch := make(chan int) // 数据传递通道
quit := make(chan bool) // 退出通道
// 使用者
go func() {
for i := 0; i < 8; i++ {
num := <-ch
fmt.Println(num)
}
quit <- true
}()
//生产者
feibo(ch, quit)
}
func feibo(ch chan<- int, quit <-chan bool) {
x, y := 1, 1
for {
select {
case ch <- x:
x, y = y, x+y
case <-quit:
fmt.Print("oks")
return
}
}
}