go语法--基础39--并发编程--案例2

一、并发和并行

Go语言为并发编程而内置的上层API基于CSP模型。

communicating sequential processes:顺序通信模型

Go语言通过安全的通道发送和接受数据以实现同步。

一般情况下,一个普通的计算机跑十几二十个线程就有点负载过大了,但是同样这台机器却可以轻松地让成百上千甚至上万个goroutine进行资源竞争。

二、goroutine

goroutine是Go并发设计的核心。
Goroutine说到底其实就是协程,但是他比线程更小,十几个goroutine可能体现在底层就是五六个线程,Go语言内部帮你实现了这些goroutine之间的内存共享。

执行goroutine只需要极少的栈内存(大概是4~5KB),当然会根据相应的数据伸缩。也正因为如此,可同时运行成千上万个并发任务。

Goroutine比thread更易用、更高效、更轻便。

2.1、goroutine退出方式

主协程退出了,其他协程也要跟着退出。

package main

import (
	"fmt"
	"time"
)

//主协程退出了,子协程也要跟着退出

func main() {

	go func() {
		i := 0
		for {
			i++
			fmt.Println("子协程 i = ", i)
			time.Sleep(time.Second)
		}
	}()
	i := 0
	for {
		i++
		fmt.Println("当主协程 i = ", i)
		time.Sleep(time.Second)

		if i == 2 { //当主协程中i=2时,主协程退出
			break
		}
	}
}


输出

当主协程 i =  1
子协程 i =  1
子协程 i =  2
当主协程 i =  2

2.2、Runtime.Gosched

用于让出CPU时间片,让出当前goroutine的执行权限,调度器安排其他等待的任务执行,并在下次某个时候从该位置恢复执行。
就像接力赛,A跑了一会碰到代码runtime.Gosched()就把接力棒交给了B了,A就歇着了,B继续跑。

package main

import (
	"fmt"
	"runtime"
)

func main() {

	go func() {
		for i := 0; i < 10; i++ {
			fmt.Println("子协程-->:", i)
		}
	}()

	for i := 0; i < 2; i++ {

		runtime.Gosched()
		fmt.Println("-------让出时间片,先让别子协程执行,执行完后,再执行主协程-------")
		fmt.Println("主协程-->:", i)
	}
}


输出

子协程-->: 0
子协程-->: 1
子协程-->: 2
子协程-->: 3
子协程-->: 4
子协程-->: 5
子协程-->: 6
子协程-->: 7
子协程-->: 8
子协程-->: 9
-------让出时间片,先让别子协程执行,执行完后,再执行主协程-------
主协程-->: 0
-------让出时间片,先让别子协程执行,执行完后,再执行主协程-------
主协程-->: 1

2.3、runtime.Goexit

立即终止当前goroutine

调度器确保所有已注册defer延迟调用被执行。

package main

import (
	"fmt"
	"runtime"
)

func test() {
	defer fmt.Println("------调度器确保所有已注册defer延迟调用被执行-----")

	// return //此函数
	fmt.Println("------Goexit执行--->:前-----")
	runtime.Goexit() //终止所在的协程
	fmt.Println("------Goexit执行--->:后-----")

}

func main() {

	go func() {
		fmt.Println("------test执行--->:前-----")

		test()

		fmt.Println("------test执行--->:后-----")
	}()
	//写一个死循环,让主协程不结束
	for {

	}
}
输出

------test执行--->:前-----
------Goexit执行--->:前-----
------调度器确保所有已注册defer延迟调用被执行-----

2.4、runtime.GOMAXPROCS()

用来设置用来并行计算的CPU核数最大值,并返回之前的值。

package main

import (
	"fmt"
	"runtime"
)

func cpu1() {

	//设置当前的cpu核数1,并返回之前pcu核数
	oldCpuNum := runtime.GOMAXPROCS(1)

	fmt.Println("cpu1-->之前pcu核数:", oldCpuNum)

	for {
		go fmt.Print(1)
		fmt.Print(0)
	}
}
func cpu2() {

	//设置当前的cpu核数2,并返回之前pcu核数
	oldCpuNum := runtime.GOMAXPROCS(2)

	fmt.Println("cpu2-->之前pcu核数:", oldCpuNum)

	for {
		go fmt.Print(1)
		fmt.Print(0)
	}
}
func main() {
	cpu1()
 	//cpu2()
}

cpu1结果

在这里插入图片描述

当runtime.GOMAXPROCS(1)时,最多同时只能有一个goroutine被执行。所以会打印很多1。过了一段时间后,GO语言调度器将其置为休眠,并唤醒另一个goroutine,这时候开始打印很多个0了

在打印的时候,goroutine是被调度到操作系统线程上的。

cpu2结果

在这里插入图片描述

当runtime.GOMAXPROCS(2)时,我们使用了2个CPU,所以两个goroutine可以一起被执行,以同样的频率交替打印0和1。

三、channel

Goroutine运行在相同的地址空间,因此访问内存必须做好同步。

Goroutine奉行通过通信来共享内存,而不是共享内存来通信。

引用类型channel是CSP模式的具体实现,用于多个goroutine通讯。其内部实现了同步,确保并发安全。

当我们复制一个channel或用于函数参数传递时,我们只是拷贝了一个channel引用,因此调用者和被调用者将引用同一个channel对象。

和其他的引用类型一样,channel的零值也是nil。

3.1、产生资源抢占案例

下面的代码会产生资源抢占

package main

import (
	"fmt"
	"time"
)

//打印机
func Printer(str string) {
	for _, char := range str {
		fmt.Printf("%c", char)
		fmt.Println("--")
		//每秒打印一个字符
		time.Sleep(time.Second)
	}
}

//person1执行完成之后,person2执行
func person1() {
	Printer("123456")
}

func person2() {
	Printer("abcdef")
}

func main() {
	//创建两个协程,代表两个人,两个人同时使用打印机
	go person1()
	go person2()

	//不让主协程结束,死循环
	for {

	}
}
 

输出
a--
1--
2--
b--
c--
3--
4--
d--
e--
5--
6f--
--


3.2、通过管道通信解决资源抢占问题

下面代码,函数都是交替执行

package main

import (
	"fmt"
	"time"
)

//全局变量,创建一个channel
var ch = make(chan int)

//打印机
func Printer(str string) {
	for _, char := range str {
		fmt.Printf("%c", char)
		fmt.Println("--")
		//每秒打印一个字符
		time.Sleep(time.Second)
	}
}

//person1执行完成之后,person2执行
func person1() {
	Printer("123456")

	//写数据到管道
	//当管道有数据,写数据就会堵塞

	ch <- 666

}

func person2() {
	//从管道读数据
	//如果通道没有数据,就会阻塞
	<-ch
	Printer("abcdef")
}

func main() {
	//创建两个协程,代表两个人,两个人同时使用打印机
	go person1()
	go person2()

	//不让主协程结束,死循环
	for {

	}
}


输出

1--
2--
3--
4--
5--
6--
a--
b--
c--
d--
e--
f--

3.3、通过channel实现同步和数据交互

package main

import (
	"fmt"
	"time"
)

func main() {
	//创建channel
	ch := make(chan string)
	defer fmt.Println("主协程结束")
	go func() {
		defer fmt.Println("子协程执行-->完毕")
		fmt.Println("子协程执行-->开始")
		for i := 1; i <= 2; i++ {
			time.Sleep(time.Second)
			fmt.Println("子协程睡眠:", i, "秒")
		}
		ch <- "子协程end"
	}()
	str := <-ch //没有数据前,阻塞
	fmt.Println("管道内容:", str)
}


输出

子协程执行-->开始
子协程睡眠: 1 秒
子协程睡眠: 2 秒
子协程执行-->完毕
管道内容: 子协程end
主协程结束

四、定时器

Timer是一个定时器,代表未来的一个单一事件,你可以告诉timer你要等待多长时间,它提供一个channel,在将来的那个时间给那个channel写入一个时间值。

4.1、通过timer实现延时:

package main

import (
	"fmt"
	"time"
)

func main() {
	//创建一个定时器,设置时间为2s,2s后,往time通道写内容(当前时间)
	//timer只会执行一次
	timer := time.NewTimer(2 * time.Second)

	fmt.Println("当前时间:", time.Now())
	//2s后,往timer.C写数据,有数据后,就可以读取
	t := <-timer.C
	fmt.Println("当前时间: ", t)
}


输出

当前时间: 2021-06-20 20:11:25.6545682 +0800 CST m=+0.004318801
当前时间:  2021-06-20 20:11:27.6555197 +0800 CST m=+2.005270301

4.2、停止定时器:

package main

import (
	"fmt"
	"time"
)

func main() {
	timer := time.NewTimer(2 * time.Second)
	fmt.Println("当前时间:", time.Now())
	go func() {
		<-timer.C
		fmt.Println("子协程可以打印了,因为定时器的时间到")
	}()

	timer.Stop() //停止定时器
	for {

	}
}


在这里插入图片描述

4.3、重置定时器:

package main

import (
	"fmt"
	"time"
)

func main() {
	timer := time.NewTimer(60 * time.Second)

	fmt.Println("当前时间:", time.Now())

	timer.Reset(1 * time.Second) //重置计时器
	t := <-timer.C
	fmt.Println("当前时间:", t)
}


在这里插入图片描述

4.4、定时触发计时器

Ticker是一个定时触发的计时器,他会以一个间隔(interval)往channel发送一个事件(当前时间),而channel的接收者可以以固定的时间间隔从chnanel中读取事件。

package main

import (
	"fmt"
	"time"
)

func main() {
	//创建定时器,每隔1秒后,定时器就会给channel发送一个时间(当前时间)
	ticker := time.NewTicker(1 * time.Second)

	go func() {
		for t := range ticker.C {
			fmt.Println("Tick at", t)
		}
	}()

	//一旦一个打点停止了,将不能再从它的通道中接收到值。我们将在运行后 1600ms停止这个打点器。
	time.Sleep(time.Second * 10)
	ticker.Stop()
	fmt.Println("Ticker stopped")

}


输出

Tick at 2021-06-20 20:44:10.8299989 +0800 CST m=+1.005164601
Tick at 2021-06-20 20:44:11.8299812 +0800 CST m=+2.005146901
Tick at 2021-06-20 20:44:12.8312571 +0800 CST m=+3.006422801
Tick at 2021-06-20 20:44:13.8298241 +0800 CST m=+4.004989801
Tick at 2021-06-20 20:44:14.8309582 +0800 CST m=+5.006123901
Tick at 2021-06-20 20:44:15.8302667 +0800 CST m=+6.005432401
Tick at 2021-06-20 20:44:16.8301526 +0800 CST m=+7.005318301
Tick at 2021-06-20 20:44:17.8297111 +0800 CST m=+8.004876801
Tick at 2021-06-20 20:44:18.8301434 +0800 CST m=+9.005309101
Ticker stopped

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值