计算机系统应用教程网站

网站首页 > 技术文章 正文

关于go语言中的协程相关知识点总结(三)

btikc 2024-10-12 10:47:24 技术文章 9 ℃ 0 评论

接上一篇关于go语言中的协程相关知识点总结(二)继续分享 关于go协程关于select的知识点

select关键字

select可以监听处理多个channel,它不是按顺序执行的,它会对所有的channel都是同进检查的,如果select中的channel没有准备好,则会进行阻塞操作,如果有多个channel同时准备好,则会随机选择一个

举个例子

package main

import (
	"fmt"
	"math/rand"
	"os"
	"strconv"
	"time"
)

func gen(min,max int ,createNum chan int,end chan bool)  {
    //使用for,是为了一直在监听
	for  {
        //使用select 可以同时监听多个channel通道
		select {
		case createNum<-rand.Intn(max-min)+min:
		case <-end:
			close(end)
			return
            //这里的time.After()做了个超时处理
		case <-time.After(4*time.Second):
			fmt.Println("time.after")
			return
		}
	}
}
func main() {
	rand.Seed(time.Now().Unix())
	createNum :=make(chan int)
	end := make(chan bool)
	if len(os.Args)!=2{
		fmt.Println("参数异常")
		return
	}
	n,_ :=strconv.Atoi(os.Args[1])
	fmt.Println("创建的随机数是",n)
	go gen(0,2*n,createNum,end)
	for i := 0; i < 20; i++ {
		fmt.Println("取值",<-createNum)
	}
    //注意下这里,由于取值完后,会等待5秒,但是time.After()只等待4秒,就会处理超时,退出等待,
	time.Sleep(5*time.Second)
	fmt.Println("退出")
	//end<-true
}

利用time.After()来检查goroutine超时的两种方式

方式一:

package main

import (
	"fmt"
	"time"
)

func main() {
	c := make(chan string)
	go func() {
		//这里主要是模拟一下业务操作用时
		time.Sleep(time.Second*3)
		c <-"c1 ok"
	}()
	select {
	case res :=<-c:
		fmt.Println("取值",res)
	case <-time.After(time.Second*1):
		fmt.Println("timout c1")

	}
}

方式二:

这里只不过,超时时间可以通过参数可以控制

package main

import (
	"fmt"
	"os"
	"strconv"
	"sync"
	"time"
)
func timeout(w *sync.WaitGroup,t time.Duration) bool{
	temp :=make(chan int)
	go func() {
		time.Sleep(time.Second*5)
		defer close(temp)
		w.Wait()
	}()
	select {
	case <-temp:
		return false
	case <-time.After(t):
		return true


	}
}
func main() {
	arg := os.Args
	if len(arg) !=2{
		fmt.Println("参数缺失")
		return

	}
	var w sync.WaitGroup
	w.Add(1)
	t,err :=strconv.Atoi(arg[1])
	if err != nil {
		fmt.Println(err)
		return
	}
	dur := time.Duration(int32(t)) * time.Millisecond
	fmt.Println("time时间",dur)
	if timeout(&w,dur){
		fmt.Println("timeout ")
	}else{
		fmt.Println("ok")
	}
	w.Done()

}

关于通道的知识点总结

  1. channel类型的零值是nil,如果给一个已关闭的channel发送消息,程序会崩溃,如果从一个一个已关闭的channel读取数据,会得到channel类型的 零值
  2. 一个nilchannel总是阻塞的,这个特性有时会用到
  3. 可缓冲通道

make(chan int ,5)//指定第二个参数,来说明可缓冲大小

通过共享变量来共享内存

当多个goroutine在任何给定时间访问共享变量,会出现数据竞争问题,这也是并发程序中需要考虑的,一个可以通过channel通道解决,也可以使用共享变量加锁实现,下面说说通过共享变量加锁的相关知识点

sync.Mutex类型

sync.Mmutex类型是Go实现的一个互斥体,可以使用sync.Lock()和sync.Unlock()来实现加锁和解锁,这两个要同时出现,如果不一致,会导致程序出现死锁

来个例子

package main

import (
	"fmt"
	"os"
	"strconv"
	"sync"
	"time"
)

var(
	m sync.Mutex
	v1 int
	w sync.WaitGroup
)

func change(i int)  {
	//加锁
	m.Lock()
	time.Sleep(time.Second)
	v1=v1+1
	if v1%10==0{
		v1=v1-10*i
	}
	//解锁
	m.Unlock()
}
func read()int  {
	m.Lock()
	a := v1
	m.Unlock()
	return a
}
func main() {
	if len(os.Args) !=2{
		fmt.Println("参数缺失")
		return
	}
	num,err :=strconv.Atoi(os.Args[1])
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(read())
	for i := 0; i < num; i++ {
		w.Add(1)
		go func() {
			defer w.Done()
			change(i)
			fmt.Println("->",read())
		}()
	}
	w.Wait()
	fmt.Println("-->",read())

}

sync.RWMutex类型

sync.RWMutex可以说是sync.Mutex的改进版,它只允许一个函数执行写操作,但可以同时允许有sync.RWMutex互斥锁的多个读取者,但是写操作就会阻塞,它有RLock()和RUnlock()是用于读操作的函数,Lock()和Unlock()用于写操作的函数

来个例子

package main

import (
	"fmt"
	"os"
	"sync"
	"time"
)
var password=secret{password: "12345"}
type secret struct{
    RWM sync.RWMutex
	M sync.Mutex
	password string
}

func Change(c *secret,pass string)  {
	c.RWM.Lock()
	fmt.Println("lchange")
	time.Sleep(time.Second*10)
	c.password=pass
	c.RWM.Unlock()
}
func Show(c *secret)string  {
	//在读取的时候加了锁,这样,就会出现在读的时候,写操作是阻塞的,保证读出来的数据是一样的
	c.RWM.RLock()
	fmt.Println("show")
	time.Sleep(time.Second*3)
	defer c.RWM.RUnlock()
	return c.password
}
func showWithLock(c *secret) string  {
	//由于这里使用了排他锁,所以同一时间只有一个函数可以读到值
	c.M.Lock()
	fmt.Println("showwithlock")
	time.Sleep(time.Second*3)
	defer c.M.Unlock()
	return c.password
}
func main() {
	var showFunc= func(c *secret) string{ return ""}
	if len(os.Args)!=2{
		fmt.Println("使用了sync.RWMutex")
		showFunc=Show
	}else{
		fmt.Println("使用了sync.Mutex")
		showFunc=showWithLock
	}
	var w sync.WaitGroup
	fmt.Println(showFunc(&password))
	for i := 0; i < 15; i++ {
		w.Add(1)
		go func() {
			defer w.Done()
			fmt.Println("go pass",showFunc(&password))
		}()
		go func() {
			w.Add(1)
			defer w.Done()
			Change(&password,"12345")
		}()
		w.Wait()
		fmt.Println("pass",showFunc(&password))
	}
}

这次主要是总结了一下关于协程间通信和数据访问竞态的知识点

下一篇主要说一下

<<如果优雅的让协程退出>>

关注我,不迷路!!!!

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表