Golang并发

77 阅读6分钟

Goroutine

Goroutine是Go语言中用于并发执行的轻量级线程,它由Go运行时(runtime)管理。在Go程序中,你可以通过关键字"go"来创建一个Goroutine。 Goroutine的创建非常简单,只需要在函数调用前加上"go"关键字即可。

J2QT2ED[EV43]1`%O)PQ7(5.png

例:

	println("hello goRoutine:" + fmt.Sprint(i))
}

func HelloGoRoutine() {
	for i := 0; i < 5; i++ {
		go func(j int) { //开启一个协程: go+函数
			hello(j)
		}(i)
	}
	time.Sleep(time.Second)
}

func main() {
	HelloGoRoutine()
}

CSP

CSP(Communicating Sequential Processes)是一种并发计算的模型,它将并发系统中的并发操作抽象为独立运行的进程(Process),这些进程通过消息传递的方式进行通信和同步。

在CSP模型中,各个进程是独立运行的,它们没有共享的内存空间,只能通过发送和接收消息来进行通信。进程之间通过通道(Channel)进行消息的传递,一个进程发送消息到通道,另一个进程从通道中接收消息。

通过通讯共享内存 ![I4}6ZLM5WT](10I5XS2N9SC.png](p9-juejin.byteimg.com/tos-cn-i-k3…?)

CSP模型的特点包括:

  1. 并行性:CSP模型适用于描述并发执行的系统,各个进程可以并行地执行。
  2. 通信顺序性:CSP模型中,消息的发送和接受是按照一定的顺序进行的。一个进程发送的消息必须要有另一个进程接收,否则发送操作会被阻塞。
  3. 同步:通过通信来实现同步,进程之间可以通过发送和接收消息来进行同步操作,确保进程之间的顺序执行。
  4. 无共享内存:CSP模型中的进程之间没有共享的内存空间,进程之间唯一的交互方式就是通过消息传递。

并发安全LOCK

通过使用LOCK来保证某一个协程进行完再走写一个


var (
	x    int64
	lock sync.Mutex
)

// 使用了并发安全Lock
func addWithLock() {
	for i := 0; i < 2000; i++ {
		//先获取这个资源权限
		lock.Lock()
		x += 1
		//再释放权限
		lock.Unlock()
	}
}

func addWithoutLock() {

	for i := 0; i < 2000; i++ {
		x += 1
	}
}
func main() {
	x = 0
	for i := 0; i < 5; i++ {
		go addWithoutLock()
	}
	time.Sleep(time.Second)

	println("WithoutLock:", x)

	x = 0
	for i := 0; i < 5; i++ {
		go addWithLock()
	}
	time.Sleep(time.Second)
	println("WithLock:", x)
}

//WithoutLock: 8277
//WithLock: 10000
//有lock的协程会让x每次加完才走下一步,而没有lock会出现x漏加的情况(因为是并发执行)

Channel

通道(Channel)是Go语言中用于在不同Goroutine之间进行通信和同步的一种机制。通道提供了一种安全、简单和高效的方式来传递数据。

在Go语言中,你可以通过内置的make函数来创建一个通道。通道可以指定传递的数据类型:

ch := make(chan int) // 创建一个传递int类型数据的通道  (src)

通过通道,你可以使用箭头操作符<-来发送和接收数据。发送数据的语法是 通道 <- 值,接收数据的语法是值 := <- 通道

HG8G8IFL(RI~(OZ8ELQ91RY.png

实例测试:

//通过通信来实现共享内存

//A子协程发送0-9数字
//B子协程计算输入数字的平方
//主协程输出最后平方数

func CalSquare() {
	src := make(chan int)     //无缓存队列,速度快
	dest := make(chan int, 3) //有缓存,解决生产和消费速度,

	//A协程
	go func() {
		defer close(src)
		for i := 0; i < 10; i++ {
			src <- i
		}
	}()

	//B协程
	go func() {
		defer close(dest)
		for i := range src {
			dest <- i * i
		}
	}()

	for i := range dest {
		//复杂操作
		println(i)
	}
}

func main() {
	CalSquare()
}

输出: 0 1 4 9 16 25 36 49 64 81

在上面的例子中,我们创建了两个整型通道 src 和 dest 。在一个Goroutine中,我们发送了值到通道 src 。然后,在主Goroutine中,我们从通道 src 接收到了值,B协程遍历 src 把值发送给 desc 通道。

通道的发送和接收操作是阻塞的,这意味着发送者和接收者在进行操作时会等待对方完成。如果发送者向一个已满的通道发送数据,发送操作会被阻塞,直到有空闲的位置。如果接收者从一个空的通道接收数据,接收操作会被阻塞,直到有可用的数据。

通道还可以通过指定缓冲区大小来创建带有缓冲区的通道。带有缓冲区的通道可以在缓冲区未满时进行发送,只有在缓冲区已满或被读取之后才会阻塞发送操作。(dest)

优点:使用通道可以实现多个Goroutine之间的同步和通信,有效地避免了竞态条件和资源争用问题,使得并发程序更加安全和可靠。同时,通道也是Go语言中实现多种并发设计模式的重要组成部分。

WaitGroup

WaitGroup是Go语言中用于等待一组Goroutine完成执行的一种同步机制。通过WaitGroup,我们可以等待一组Goroutine全部执行完毕,然后再继续执行后续的操作。

WaitGroup的使用包括三个主要步骤:

  1. 创建WaitGroup对象:通过调用sync.WaitGroupAdd方法来创建一个WaitGroup对象,并初始化其计数器。
var wg sync.WaitGroup
  1. 增加计数器:在每个需要等待的Goroutine之前,调用WaitGroup的Add方法,增加计数器的值。

  2. 减少计数器:在Goroutine执行完成后,调用WaitGroup的Done方法,减少计数器的值。

  3. 等待完成:在主Goroutine中,调用WaitGroup的Wait方法,阻塞等待所有计数器归零。

实例:

func HelloGoRoutine() {
	var wg sync.WaitGroup //声明
	wg.Add(5)             //要开启5个协程
	for i := 0; i < 5; i++ {
		go func(j int) { //开启一个协程: go+函数
			defer wg.Done() //计算器-1;表名这个协程已结束
			hello(j)
		}(i)
	}
	wg.Wait() //阻塞直到计算器为0
}

func main() {
	HelloGoRoutine()
}

这段代码中定义了一个函数HelloGoRoutine(),该函数会开启5个Goroutine并行执行hello()函数。

HelloGoRoutine()函数中,首先声明了一个sync.WaitGroup对象wg用来等待所有的Goroutine完成执行。然后使用wg.Add(5)将计数器设置为5,表示需要开启5个Goroutine。

接下来使用一个for循环来开启5个Goroutine。在每个Goroutine中,使用go func(j int)来定义一个匿名函数作为Goroutine的执行体。通过使用j参数将当前迭代变量的值传递给匿名函数,确保每个Goroutine都能获取到正确的索引值。

在匿名函数中,使用defer wg.Done()将计数器减一,表示当前Goroutine执行结束。然后调用hello(j)函数进行具体的逻辑处理。

主函数main()中调用了HelloGoRoutine()函数来执行。在HelloGoRoutine()函数执行的过程中,主函数会等待所有Goroutine执行完成,即wg.Wait()

优点:通过这样的方式,可以实现多个Goroutine并行执行而不用担心它们的执行时机,同时还可以确保所有Goroutine执行完毕后再进行后续的操作。