go语言的基础知识4.1(创建协程+互斥锁)

知识点:

  1. go关键字创建协程
  2. wg sync.WaitGroup 的 add done wait 三个函数用于程序等待n个协程结束的效果
  3. 在协程中 互斥锁和读写锁的使用(好像一般我们都用chanel,不大用锁,因为chanel自带锁)
package main

import (
	"fmt"
	"strconv"
	"sync"
)

func main() {
	fmt.Println("---")
	GroupGoRoutine()
}

/*
下面三者之间的概念的解释:
进程:进程是系统 进行资源分配和调度的一个独立单位。
线程:线程可与同属一个进程的其它线程共享进程所拥有的全部资源(线程通信主要通过共享内存)
协程:协程是一种用户态的轻量级线程,协程的调度完全由用户控制。默认的所有的goroutine 会在一个原生线程里跑,也就是只使用了一个CPU核。注意写的是默认的!默认的!!。

"默认的默认的"是指 Go 语言中的默认行为,也就是在没有特别指定的情况下,所有的 Goroutines(协程)都会运行在同一个原生线程中。
这意味着默认情况下,多个 Goroutines 会共享同一个操作系统线程,而不会在不同的操作系统线程中运行。
在 Go 语言中,Goroutines 是一种轻量级的并发执行单元,它们的创建和管理相对于传统的线程来说非常高效。
默认情况下,Go 会使用一种称为 "调度器" 的机制,将多个 Goroutines 安排在同一个操作系统线程中运行,以充分利用计算资源。

这种默认行为的好处是简化了并发编程,使得开发者不需要过多关心线程和线程之间的交互问题。
开发者可以专注于编写 Goroutines,而不需要手动管理线程池等细节。
但需要注意的是,虽然多个 Goroutines 可以运行在同一个操作系统线程中,但 Go 语言的调度器会根据一定的策略在不同的 Goroutines 之间进行切换,以保证程序的并发性。这使得 Goroutines 仍然可以充分利用多核 CPU。
如果需要更精细的控制,Go 语言也提供了一些方式来指定 Goroutines 运行在不同的操作系统线程中,例如使用 runtime.GOMAXPROCS 函数。但对于大多数情况,使用默认行为即可,因为 Go 的调度器已经被设计得非常智能和高效。
*/

/*
协程之间想要数据传递该怎么办?
使用 go 关键字创建 goroutine 时,被调用函数的 返回值 会被忽略。传递数据可以使用共享变量。
如果需要在 goroutine 中返回数据,需要使用 通道(chan)特性,通过通道把数据从 goroutine 中作为返回值传出。(这个例子没用到通道)
*/

/*
如何创建一个协程?
可使用Go关键字创建协程:1.go关键字+普通函数  2.go关键字+匿名函数
*/

/*
使用锁完成多个协程之间的协同工作。
互斥锁,用于确保在多个协程同时访问共享资源 cnt 变量时的数据安全性。
在多个协程并发执行的情况下,如果不使用互斥锁进行保护,可能会导致竞态条件,从而导致不可预测的结果。

不同协程可能同时读取 cnt 的当前值,然后进行递增,导致多次递增操作只增加了一次 cnt 的值。
不同协程可能在不同的时间点写入 cnt,这可能导致数据损坏或不一致。
使用互斥锁可以解决这些问题。当一个协程获得了锁(通过 countMux.Lock()),其他协程将被阻塞,直到锁被释放。
这确保了只有一个协程可以同时访问共享资源,从而避免了竞态条件。

其它锁的概念
// 定义一个读写锁
// var rwMux sync.RWMutex
// // 锁住需要写入的数据
// rwMux.Lock()
// // 释放锁
// rwMux.UnLock()
*/
var (
	wg         sync.WaitGroup
	GlobalNode string     // 多个协程的共享变量
	cnt        int        // 多个协程的共享变量
	countMux   sync.Mutex // cnt 变量的互斥锁

	dataChan = make(chan string)
)

//-- 此函数为模拟一个协程的工作
func printInfo(s string) {

	//-- 表明一个协程处理结束
	defer wg.Done()
	//-- 加锁和解锁是为了确保在多个协程同时访问共享资源(例如 cnt 和 GlobalNode 变量)时的数据安全性。互斥锁确保了在一个协程修改 cnt 变量时,其他协程不能同时修改,从而保证了数据一致性。
	countMux.Lock()
	defer countMux.Unlock()

	fmt.Println("GlobalNode_Begin:", GlobalNode)
	GlobalNode = s
	fmt.Println("GlobalNode_End:", GlobalNode)

	//-- 模拟一个耗时的操作
	for i := 0; i < 100000; i++ {
		//fmt.Println("Info =", s, "i =", i)
		cnt++
		// 延时1秒  如果好几个协程都在Sleep 这个时间会非常长
		//time.Sleep(time.Second)
	}
}

/*
演示协程的创建和等待
sync 包提供了 sync.WaitGroup 类型,它是一种用于等待一组协程完成执行的机制。
sync.WaitGroup 类型通常与 Add、Wait 和 Done 方法一起使用,以实现等待协程结束的效果。

Add(delta int) 方法:Add 方法用于向 WaitGroup 中添加一个或多个等待的协程数量。
delta 参数表示要添加的协程数量,通常在创建协程之前使用。
每个协程都会调用 Add 方法来通知 WaitGroup 要等待的协程数量增加了。

Wait 方法:Wait 方法用于等待 WaitGroup 中的计数器归零,也就是等待所有添加的协程都完成。
如果计数器不为零,则 Wait 方法会阻塞当前协程,直到计数器变为零为止。

Done 方法:Done 方法用于通知 WaitGroup 一个协程已经完成,可以减少计数器的值。
通常在协程的最后调用 Done 方法来表示协程已完成。


*/
func GroupGoRoutine() {

	//-- 例子1
	//-- 设置需要等待的协程数:使用go关键字开几个协程就add几个协程,不对应会报错。在协程中也Done表明协程处理结束,否则也会报错。
	wg.Add(2)
	go printInfo("==Routine1==")
	go func() {
		defer wg.Done() //-- 通常在协程的最后,调用 Done 方法来表示协程已完成。
		num := testRoute()
		fmt.Println("num=", num)
	}()

	//-- 例子2(可以例子1、2一起实验,也可以1和2分开实验)
	wg.Add(2)
	var str string
	for i := 0; i < 2; i++ {
		str = "Id:"
		str = str + (strconv.Itoa(i))
		go printInfo("=" + str + "=")
	}

	//等待 所有的协程结束(如果不等待, 主线程可能先被执行完, 协程得不到时间去执行)
	wg.Wait()
	fmt.Println("cnt =", cnt)
}

func testRoute() int {
	return 1
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值