GO语言学习(七)---并发编程

一、协程

golang中的并发是函数独立运行的能力。Goroutines是并发运行的函数。Golang提供了Goroutines作为并发处理操作的一种方式。

创建一个协程非常简单,就是在一个任务函数前面添加一个go关键字

go task()

举例:

1、只有一个协程,即主协程

package main

import (
	"fmt"
	"time"
)
/*
golang 协程
*/
func showMeg(msg string){
	for i := 0; i < 5; i++ {
		fmt.Printf("msg:%v\n", msg)
		time.Sleep(time.Millisecond*100)
	}
}


func main() {
	showMeg("java") //加了go关键字,启动了一条协程
	showMeg("golang")
	fmt.Println("end...")
}

运行结果

 

原因:只有一个协程,所以先执行showMsg(java),再执行showMsg(go),再输出end

2、有两个协程

1)先go

package main

import (
	"fmt"
	"time"
)
/*
golang 协程
*/
func showMeg(msg string){
	for i := 0; i < 5; i++ {
		fmt.Printf("msg:%v\n", msg)
		time.Sleep(time.Millisecond*100)
	}
}


func main() {
	go showMeg("java") //加了go关键字,启动了一条协程
	showMeg("golang")
	fmt.Println("end...")
}

运行结果

原因:

showMsg(java)开启了一个协程,main函数有一个主协程,所以一共有两个协程。showMsg(java)开启的协程负责打印java,后者负责打印GO和end...二者交替执行。

2)后 Go

package main

import (
	"fmt"
	"time"
)
/*
golang 协程
*/
func showMeg(msg string){
	for i := 0; i < 5; i++ {
		fmt.Printf("msg:%v\n", msg)
		time.Sleep(time.Millisecond*100)
	}
}


func main() {
	showMeg("java") 
	go showMeg("golang")加了go关键字,启动了一条协程
	fmt.Println("end...")
}

运行结果

showMsg(Go)开启了一个线程,负责打印GO,主协程打印java和end...,但是由于开启showMsg(Go)协程时,java已经打印完,所以主协程在打印完end...之后就结束了。主协程一旦结束,就会自动结束其他的协程

3、有三个协程

package main

import (
	"fmt"
	"time"
)
/*
golang 协程
*/
func showMeg(msg string){
	for i := 0; i < 5; i++ {
		fmt.Printf("msg:%v\n", msg)
		time.Sleep(time.Millisecond*100)
	}
}


func main() {
	go showMeg("java") //协程一
	go showMeg("golang")//协程二
	fmt.Println("end...") //协程三-主协程
}

运行结果

end...或者end... msg:java;

发现运行结果不一样且没怎么打印原因:

三个协程同时运行。如果协程三,即主协程退出了,就会自动结束协程一二

所以打印结果会出现上面这种情况。

解决办法:另主协程等待一段时间再执行:

package main

import (
	"fmt"
	"time"
)
/*
golang 协程
*/
func showMeg(msg string){
	for i := 0; i < 5; i++ {
		fmt.Printf("msg:%v\n", msg)
		time.Sleep(time.Millisecond*100)
	}
}


func main() {
	go showMeg("java") //协程一
	go showMeg("golang")//协程二
	time.Sleep(time.Millisecond*2000)  //另主协程等待一段时间再执行
	fmt.Println("end...") //协程三-主协程
}

运行结果

二、channel

channel(通道)用于在协程之间共享数据。通道分为两种:有缓冲通道、无缓冲通道。无缓冲通道用于执行协程之间的同步通信,缓冲通道用于执行异步通信。无缓冲通道保证在发送和接受发生的瞬间执行两个协程之间的交换,缓冲通道没有这样的保证

1、语法

	Unbuffered := make(chan int)
	buffered := make(chan int,10)	

使用内置函数make创建无缓冲和缓冲通道,make的第一个参数需要关键字chan,然后是通道允许交换的数据类型。如果是有缓存通道需要指定缓冲区大小,即第二个参数

2、向通道传递

channel<- value //把value传递到通道
value:=<-channel //从通道中获取信息存入value

3、示例

package main

import (
	"fmt"
	"time"
	"math/rand"
)
/*
golang chnnel通道
*/

var values = make(chan int)
func send(){
	rand.Seed(time.Now().UnixNano())
	value:=rand.Intn(10)
	fmt.Printf("send:%v\n", value)
	channel<- value
}

func main() {
	//从通道接收值
	defer close(values) //延迟退出,只有main主函数退出的时候,通道才会关闭
	go send()
	fmt.Println("wait...")
	value:=<-channel
	fmt.Printf("receive:%v\n", value)
	fmt.Println("end...")
}

/*
wait...
send:9
receive:9
end...
*/

一共有两个协程:send()和主函数协程。send()通过通道values向主函数发送value,主函数通过通道接收

三、WaitGroup 实现同步

同步:两个协程之间互相等待

package main

import (
	"fmt"
	// "time"
	// "math/rand"
	"sync"  //waitGroup在这个包下面
)
/*
golang WaitGroup实现同步
*/

var wg sync.WaitGroup //使用waitGroup

func hello(i int)  {
	defer wg.Done() //协程结束waitgroup数记减一
	fmt.Println("hello Goroutine",i)
}


func main() {
	for i := 0; i < 10; i++ {
		wg.Add(1) //每执行一个协程就waitgroup数加1
		//启动一个协程来执行
		go hello(i)
	}
	//主协程
	wg.Wait()
	fmt.Println("end...")
}

/*
如果没有加group的执行结果 并不是0-9都打印出来了。这是因为:
一旦主协程结束了,其他的协程就会被强制终止执行
end...
hello Goroutine 5
*/
/*
解决办法:使用waitGroup来完成协程之间的同步
hello Goroutine 9
hello Goroutine 4
hello Goroutine 5
hello Goroutine 3
hello Goroutine 1
hello Goroutine 6
hello Goroutine 7
hello Goroutine 8
hello Goroutine 2
hello Goroutine 0
end...

*/

四、runtime包

runtime包里面定义了一些协程管理相关的kpi

1、runtime.Gosched()

让出CPU时间片,重新安排等待任务---当前时间片轮到我执行了,但是我不执行,我让出cpu,给其他协程

package main

import (
	"fmt"
	"runtime"
	// "time"
	// "math/rand"
	// "sync"  //waitGroup在这个包下面
)
/*
golang runtime
*/
func show(msg string){
	for i := 0; i < 2; i++ {
		fmt.Printf("msg:%v\n", msg)
	}
}


func main() {
	go show("java") //启动子协程
	//加入runtime
	for i := 0; i <2; i++ {
		runtime.Gosched()//我有权力执行任务了,让给其他子协程来执行
		
	}
	fmt.Println("end....")
}
/*
没有使用runtime包时的运行结果:因为主协程运行完之后会把子协程强制关闭
end....
或者
end....
msg:java
*/

/*
使用runtime 在主协程可以执行时,不执行,让给子协程来执行。从而子协程可以执行完
有可能让cpu的第一次子协程就执行完了,那么第二次就让不出去了,只能主协程自己执行
也有可能cpu第二从让给子协程,子协程才执行完
msg:java
msg:java
end....
*/

2、runtime.Goexit()

退出当前协程

package main

import (
	"fmt"
	"runtime"
	"time"
	// "math/rand"
	// "sync"  //waitGroup在这个包下面
)
/*
golang runtime
*/
func show(){
	for i := 0; i < 10; i++ {
		fmt.Printf("i:%v\n",i)
		if i>=5{
			runtime.Goexit()
		}
	}
}


func main() {
	go show() 
	time.Sleep(time.Second)

}
/*
i:0
i:1
i:2
i:3
i:4
i:5
*/

3、runtime.GOMAXPROCS

package main

import (
	"fmt"
	"runtime"
	"time"
	// "math/rand"
	// "sync"  //waitGroup在这个包下面
)
/*
golang runtime
*/
func showa(){
	for i := 0; i < 10; i++ {
		fmt.Printf("a:%v\n",i)
	}
}
func showb(){
	for i := 0; i < 10; i++ {
		fmt.Printf("b:%v\n",i)
	}
}


func main() {
	fmt.Printf("runtime.NumCPU():%v\n", runtime.NumCPU())  //16
	runtime.GOMAXPROCS(2) //设置CPU核心数,如果不设置就取最多的
	go showa()
	go showb()
	time.Sleep(time.Second)

}
/*
如果设置最大cpu核心数是1,都是b先执行再a,或者a先b后,不会交替
b:0
b:1
b:2
b:3
b:4
b:5
b:6
b:7
b:8
b:9
a:0
a:1
a:2
a:3
a:4
a:5
a:6
a:7
a:8
a:9
*/
/*
如果cpu核心数设置为大于1的数,则会交替执行
runtime.NumCPU():16
a:0
a:1
a:2
a:3
a:4
a:5
b:0
b:1
b:2
b:3
b:4
b:5
b:6
b:7
b:8
b:9
a:6
a:7
a:8
a:9
*/

五、Mutex互斥锁实现同步

除了使用channel实现同步之外,还可以使用Mutex互斥锁实现同步

package main

import (
	"fmt"
	// "runtime"
	"time"
	// "math/rand"
	"sync"  //waitGroup在这个包下面
)
/*
golang mutex
*/
var i int =100
var wg sync.WaitGroup
var lock sync.Mutex
func add(){
	defer wg.Done()
	lock.Lock() //加锁
	i+=1
	fmt.Printf("i++:%v\n", i)
	time.Sleep(time.Millisecond*10)
	lock.Unlock() //解锁
}
func sub(){
	defer wg.Done()
	lock.Lock() //加锁
	i-=1
	fmt.Printf("i--:%v\n", i)
	time.Sleep(time.Millisecond*2)
	lock.Unlock() //解锁
}


func main() {
	for i := 0; i < 100; i++ {
		wg.Add(1)
		go add()
		wg.Add(1)
		go sub()
	}
	wg.Wait()
	fmt.Printf("end i:%v\n", i)

}
/*
没有使用协程,运行结果
i++:101
i--:100
i++:101
i--:100
i++:101
i--:100
.......
end i:100
*/
/*
使用协程 出现了不一致的结果 
end i:99
i++:97
*/
/*
使用mutex进行加锁,完成对于共享资源的完整访问与更改

*/

六、channel的遍历

注意:如果写的时候不关闭通道,在发生读比写多这种情况时,会发生死锁;其他情况不会。

package main

import (
	"fmt"
	// "runtime"
	// "time"
	// "math/rand"
	// "sync"  //waitGroup在这个包下面
)
/*
golang channe的遍历
*/
var c=make(chan int)

func main() {
	go func(){
		for i := 0; i < 2; i++ {
			c<-i
		}
		close(c)//关闭通道
	}()
	//方式一
	for i := 0; i < 3; i++ {
		r:=<-c
		fmt.Printf("r:%v\n", r)
	}
	//方式二
	for v := range c {
		fmt.Printf("v:%v\n", v)
	}
	//方式三:
	for{
		v,ok:=<-c
		if ok{
			fmt.Printf("v:%v\n", v)
		}else{
			break
		}
	}
	// r:=<-c
	// fmt.Printf("r:%v\n", r)	
	// r=<-c
	// fmt.Printf("r:%v\n", r)
	// r=<-c
	// fmt.Printf("r:%v\n", r)	

}

七、select

package main

import (
	"fmt"
	// "runtime"
	"time"
	// "math/rand"
	// "sync"  //waitGroup在这个包下面
)
/*
golang select
*/
var chanInt=make(chan int,0)
var chanStr=make(chan string)

func main() {
	go func(){
		chanInt<-100
		chanStr<-"zxx"
		close(chanInt)
		close(chanStr)
	}()
	for{
		select{
		case r:=<-chanInt:
			fmt.Printf("chanInt:%v\n", r)
		case r:=<-chanStr:
			fmt.Printf("chanStr:%v\n", r)
		default:
			fmt.Println("default...")
		}
		time.Sleep(time.Second)
	}

}

运行结果:

分析:读出100和zxx之后,通道里就没有东西了,所以每次读出都是默认值(如果defer close不写,即不关闭通道。那么读比写多就会造成死锁了)

八、Timer 

实现一些定时的操作,内部也是通过channel来实现的

package main

import (
	"fmt"
	// "runtime"
	"time"
	// "math/rand"
	// "sync"  //waitGroup在这个包下面
)
/*
golang timer
*/


func main() {
	//方式一
	timer:=time.NewTimer(time.Second*2)//声明一个timer,等待两秒钟
	fmt.Printf("time.Now():%v\n", time.Now())//读取当前的时间 2023-10-23 19:24:30.7490238
	t1:=<-timer.C // 阻塞,直到计时器触发 理解:timer是个容器,我们写入计时器
	//timer.C是计时器的通道,这里面是要等待时间。取出来之后,阻塞。
	fmt.Printf("t1:%v\n", t1) //2023-10-23 19:24:32.7523558

	//方式二:
	fmt.Printf("time.Now():%v\n", time.Now())
	timer1:=time.NewTimer(time.Second*2)
	<-timer1.C
	fmt.Printf("timer1:%v\n",timer1)
	//方式三:
	time.Sleep(time.Second)
	//方式四:调用After
	<-time.After(time.Second*2)
	//方式五:stop 停止计时器
	timer2:=time.NewTimer(time.Second) //等待一秒钟
	go func(){
		<-timer2.C
		fmt.Println("func...")
	}()
	s:=timer2.Stop()
	if s{
		fmt.Println("stop...")
	}
	time.Sleep(time.Second*3)
	fmt.Println("main end...")
	/*
	运行结果
	stop...
	main end...
	原因:timer2是一个计时器。timer2.stop停止了计时器。所以有关计时器的都不会
	执行了,例如匿名函数,所以不会输出func...
	*/
	//reset方法 更改等待时间
	fmt.Println("before")
	timer4:=time.NewTimer(time.Second*2)
	timer4.Reset(time.Second)
	<-timer4.C
	fmt.Println("after")

}

九、Ticker

timer只执行一次,Ticker可以周期性执行

package main

import (
	"fmt"
	// "runtime"
	"time"
	// "math/rand"
	// "sync"  //waitGroup在这个包下面
)
/*
golang timer
*/
func main() {
	ticker:=time.NewTicker(time.Second)
	counter:=1
	for _ = range ticker.C {
		fmt.Println("ticker...") //每隔一秒钟执行一次
		counter++
		if counter>=5{
			ticker.Stop() //停止计时器
			break //退出函数
		}
	}
}
package main

import (
	"fmt"
	// "runtime"
	"time"
	// "math/rand"
	// "sync"  //waitGroup在这个包下面
)
/*
golang timer
*/
func main() {
	ticker:=time.NewTicker(time.Second)
	chanInt:=make(chan int)
	//写

	go func(){
		for _= range ticker.C {
			select{
			case chanInt<-1:
			case chanInt<-2:
			case chanInt<-3:
			}
		}
	}()
	//读
		sum:=0
		for v := range chanInt {
			fmt.Println("收到:",v)
			sum+=v
			if sum>=10{
				break
			}
		}
	
	}
	/*
	收到: 3
	收到: 1
	收到: 1
	收到: 2
	收到: 1
	收到: 3
	
	*/

十、原子变量

1、通过加锁,实现写成之间的同步

package main

import (
	"fmt"
	// "runtime"
	"time"
	// "math/rand"
	"sync"  //waitGroup在这个包下面
	"sync/atomic" //原子操作包
)
/*
golang 原子操作
*/

var i =100
//互斥操作--定义锁 加锁之后就可以实现线程之间的同步
var lock sync.Mutex
func add(){
	lock.Lock()
	i++
	lock.Unlock()
}
func sub(){
	lock.Lock()
	i--
	lock.Unlock()
}
func main() {
	for i := 0; i < 100; i++ {
		go add()
		go sub()
	}
	time.Sleep(time.Second*2) //如果不等一会儿,主协程就退出了,子协程就会被强制停止
	fmt.Println("i:",i)
	}

2、通过原子操作

package main

import (
	"fmt"
	// "runtime"
	"time"
	// "math/rand"
	"sync"  //waitGroup在这个包下面
	"sync/atomic" //原子操作包
)
/*
golang 原子操作
*/

var i int32=100 //注意这个类型
var lock sync.Mutex
func add(){
	//原子操作
	atomic.AddInt32(&i,1) //atomic.AddInt32(取要使用的变量的指针,操作数)
}
func sub(){
	atomic.AddInt32(&i,-1)
}
func main() {
	for i := 0; i < 100; i++ {
		go add()
		go sub()
	}
	time.Sleep(time.Second*2) //如果不等一会儿,主协程就退出了,子协程就会被强制停止
	fmt.Println("i:",i)
}

原子操作详解

atomic常见操作:

  • 增减
  • 载入 read
  • 比较并交换cas
  • 交换
  • 存储 write
package main

import (
	"fmt"
	// "runtime"
	// "time"
	// "math/rand"
	// "sync"  //waitGroup在这个包下面
	"sync/atomic" //原子操作包
)
/*
golang 原子操作
*/

func add_sub(){
	//增减
	var i int32=100 
	atomic.AddInt32(&i,1)
	fmt.Println("i:",i) //101
	atomic.AddInt32(&i,-1)
	fmt.Println("i:",i) //100

	var i1 int64=200 
	atomic.AddInt64(&i1,1)
	fmt.Println("i1:",i) //101
	atomic.AddInt64(&i1,-1)
	fmt.Println("i1:",i) //100
}

func load_store(){
	//载入和存储(读写)
	var i int32=100 
	atomic.LoadInt32(&i) //载入 load=read 读的时候保持原子 100
	fmt.Println("i:",i)
	atomic.StoreInt32(&i,200) //读出 write 写的时候保持并发 200
	fmt.Println("i:",i)
}

func cas(){
	//cas  根基
	var i int32=100 
	b:=atomic.CompareAndSwapInt32(&i,100,200)
	fmt.Printf("b:%v\n", b) //true
	fmt.Printf("i:%v\n", i) //200
	/*
	func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)
	addr:一个指向要进行比较和交换的 int32 值的指针。
	old:期望的旧值。如果 addr 中的值等于 old,则执行交换操作。
	new:要设置的新值。如果 addr 中的值等于 old,则将 addr 的值设置为 new。
	所以,如果有一个协程过来修改了i,则该函数返回false,表示无法将i修改为新值	
	*/

}
func main() {
	
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值