go并发编程

这篇具有很好参考价值的文章主要介绍了go并发编程。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

go的GMP并发模型,让go天然支持高并发,先了解一下GMP模型吧

GMP G协程,M工作线程、P处理器,M必须持有P才可以执行G
P维护着一个协程G队列,P依次将G调度到M中运行
if M0中G0发生系统调用,M0将释放P,冗余的M1获取P,继续执行P队列中剩余的G。(只要P不空闲就充分利用了CPU)
G0系统调用结束后,如果有空闲的P,则获取P继续执行G0,否则将G0放入全局队列,M0进入缓存池睡眠。(全局队列中的G主要来自从系统调用中恢复的G)

下面介绍一下编程常用的同步(synchronize)原语

互斥锁 mutex rwmutex,要了解自旋和饥饿模式
自旋最多4次,cpu核数要大于1,Processor大于1
饥饿模式阻塞超过1ms,饥饿模式下不会启动自旋过程

channel go的精华所在,注意不能关闭已经关闭的channel,不能向已关闭的channel写数据,会panic

Once

多用于实现单例模式

饿汉模式,一般是直接创建一个包级变量直接使用即可,注意既然是单例模式,就不能让他人随意创建,类型要是私有的,使用接口暴露方法,让外部获得私有变量

懒汉模式,在第一次使用时创建,这里需要注意并发安全,可以使用sync.Once来保证并发安全

type Singleton interface {
	Work() string
}

type singleton2 struct{}

func (s *singleton2) Work() string {
	return "singleton2 is working"
}

func newSingleton2() *singleton2 {
	return &singleton2{}
}

var (
	instance *singleton2
	once     sync.Once
)

// GetSingleton2 用于获取单例模式对象
func GetSingleton2() Singleton {
	once.Do(func() {
		instance = newSingleton2()
	})
	return instance
}

Pool

sync.Pool 是 Go 语言标准库中的一个并发安全的对象池,用于缓存和重用临时对象,以提高性能。通常用于减少内存分配和垃圾回收的开销。PS:一般将变量Put回Pool中,需要将变量重置

var studentPool = sync.Pool{
	New: func() any {
		return new(Student)
	},
}
func BenchmarkUnmarshalWithPool(b *testing.B) {
	for n := 0; n < b.N; n++ {
		stu := studentPool.Get().(*Student)
		json.Unmarshal(buf, stu)
		studentPool.Put(stu)
	}
}

Cond

Cond是条件变量,可以让一组goroutine等待某个条件的发生。当条件发生时,调用Broadcast或者Signal来通知所有等待的goroutine继续执行。
需要注意的是 sync.Cond 都要在构造的时候绑定一个 sync.Mutex。Wait() 和 Signal() 函数必须在锁保护下的临界区中执行。Wait()一般放在for循环中,因为可能会出现虚假唤醒

var mu = &sync.Mutex{}
var done = false

func read(name string, c *sync.Cond) {
	c.L.Lock()
	for !done {
		c.Wait()
	}
	log.Println(name, "reading")
	c.L.Unlock()
}

func write(name string, c *sync.Cond) {
	time.Sleep(1 * time.Second)
	c.L.Lock()
	done = true
	c.L.Unlock()
	log.Println(name, "writting finish")
	c.Broadcast()
}

func main() {
	cond := sync.NewCond(mu)
	go read("reader1", cond)
	go read("reader2", cond)
	go read("reader3", cond)
	write("writer", cond)

	time.Sleep(time.Second * 3)
}

WaitGroup

注意Wait后不能再Add,否则会panic

// WaitGroup 完美写法
func main() {
	taskNum := 3
	ch := make(chan any)

	go func() {
		group := &sync.WaitGroup{}
		for i := 1; i <= taskNum; i++ {
			group.Add(1)
			go func(i int) {
				defer group.Done()
				ch <- i
			}(i)
		}
		// 确保所有取数据的协程都完成了工作,才关闭 ch
		group.Wait()
		close(ch)
	}()

	for i := range ch {
		log.Println("goroutine ", i)
	}

	log.Println("finish")
}

扩展:errgroup,可以捕获goroutine中的panic,只要有一个goroutine出错,就会取消所有的goroutine

atomic

原子操作,用于解决并发问题,比如计数器,锁等

func main() {
	// 定义一个共享的计数器,使用 int64 类型
	var counter int64

	// 使用 WaitGroup 来等待所有 goroutine 完成
	var wg sync.WaitGroup

	// 启动多个 goroutine 来增加计数器的值
	for i := 0; i < 5; i++ {
		wg.Add(1)
		go func() {
			// 在每个 goroutine 中原子地增加计数器的值
			for j := 0; j < 100000; j++ {
				atomic.AddInt64(&counter, 1)
			}
			wg.Done()
		}()
	}

	// 等待所有 goroutine 完成
	wg.Wait()

	// 输出最终的计数器值
	fmt.Println("Final Counter Value:", atomic.LoadInt64(&counter))
}

context

WithCancel

context.WithCancel() 创建可取消的 Context 对象,即可以主动通知子协程退出。

func reqTask(ctx context.Context, name string) {
    for {
       select {
       case <-ctx.Done():
          fmt.Println("stop", name)
          return
       default:
          fmt.Println(name, "send request")
          time.Sleep(1 * time.Second)
       }
    }
}

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    go reqTask(ctx, "worker1")
    time.Sleep(3 * time.Second)
    cancel()
    time.Sleep(3 * time.Second)
}

WithValue

如果需要往子协程中传递参数,可以使用 context.WithValue()

type Options struct{ Interval time.Duration }

func reqTask(ctx context.Context, name string) {
    for {
       select {
       case <-ctx.Done():
          fmt.Println("stop", name)
          return
       default:
          fmt.Println(name, "send request")
          op := ctx.Value("options").(*Options)
          time.Sleep(op.Interval * time.Second)
       }
    }
}

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    vCtx := context.WithValue(ctx, "options", &Options{1})
    go reqTask(vCtx, "worker1")
    go reqTask(vCtx, "worker2")
    time.Sleep(3 * time.Second)
    cancel()
    time.Sleep(3 * time.Second)
}

WithTimeout

如果需要控制子协程的执行时间,可以使用 context.WithTimeout 创建具有超时通知机制的 Context 对象。

ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)

WithDeadlineWithTimeout 类似,不同的是 WithDeadline 可以指定一个具体的时间点。

ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(1*time.Second))

semaphore

用于限制对一组共享资源的访问。也可以手动用channel实现

// 创建一个具有初始计数为 2 的信号量
sem := semaphore.NewWeighted(2)

// 启动多个 goroutine 模拟并发访问
for i := 1; i <= 5; i++ {
	go func(id int) {
		fmt.Printf("Goroutine %d is trying to acquire the semaphore\n", id)

		// 尝试获取信号量,如果已经达到限制,则阻塞
		err := sem.Acquire(context.Background(), 1)
		if err != nil {
			fmt.Printf("Error acquiring semaphore in goroutine %d: %v\n", id, err)
			return
		}

		fmt.Printf("Goroutine %d has acquired the semaphore\n", id)
		time.Sleep(2 * time.Second)

		// 释放信号量
		sem.Release(1)
		fmt.Printf("Goroutine %d has released the semaphore\n", id)
	}(i)
}

// 等待所有 goroutine 完成
time.Sleep(10 * time.Second)

singleflight

golang.org/x/sync/singleflight 防止缓存击穿文章来源地址https://www.toymoban.com/news/detail-825036.html

var sf singleflight.Group
var wg sync.WaitGroup

for i := 1; i <= 5; i++ {
	wg.Add(1)
	go func(id int) {
		defer wg.Done()

		// 以 "key" 为参数调用 Do 方法
		val, err, _ := sf.Do("key", func() (interface{}, error) {
			fmt.Printf("Goroutine %d is performing the operation\n", id)
			time.Sleep(2 * time.Second)
			return fmt.Sprintf("Result from goroutine %d", id), nil
		})

		if err != nil {
			fmt.Printf("Goroutine %d encountered an error: %v\n", id, err)
		} else {
			fmt.Printf("Goroutine %d got result: %v\n", id, val)
		}
	}(i)
}

wg.Wait()

到了这里,关于go并发编程的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • go并发编程基础

    WaitGroup就是等待所有的goroutine全部执行完毕,add方式和Down方法要配套使用 开启两个gorountine对total进行相同此时的加减,但是这一段程序的运行结果每一次都不一样 资源竞争,加锁 加锁之后代码成功运行 锁不能复制。 更加优雅的方式,使用golang的原子包 lock相对atomic性能较

    2024年02月11日
    浏览(33)
  • GO语言网络编程(并发编程)Channel

    1.1.1 Channel 单纯地将函数并发执行是没有意义的。函数与函数间需要交换数据才能体现并发执行函数的意义。 虽然可以使用共享内存进行数据交换,但是共享内存在不同的goroutine中容易发生竞态问题。为了保证数据交换的正确性,必须使用互斥量对内存进行加锁,这种做法势

    2024年02月09日
    浏览(44)
  • GO语言网络编程(并发编程)select

    1.1.1 select多路复用 在某些场景下我们需要同时从多个通道接收数据。通道在接收数据时,如果没有数据可以接收将会发生阻塞。你也许会写出如下代码使用遍历的方式来实现: 这种方式虽然可以实现从多个通道接收值的需求,但是运行性能会差很多。为了应对这种场景,G

    2024年02月09日
    浏览(47)
  • 【Go】Go语言并发编程:原理、实践与优化

    在当今的计算机世界,多核处理器和并发编程已经成为提高程序执行效率的关键。Go语言作为一门极富创新性的编程语言,凭借其强大的并发能力,在这方面表现出色。本文将深入探讨Go语言并发编程的原理,通过实际代码示例展示其应用,并讨论可能的优化策略。 在了解G

    2024年02月10日
    浏览(41)
  • go关于并发编程的操作

    多线程程序在一个核的CPU运行 多线程程序在多个核的CPU运行 协程:用户态,轻量级线程,栈KB级别,创建和调度由go语言直接调度 线程:内核态,线程跑多个协程,栈MB级别 go直接可以开启新的协程 CSP 是 Communicating Sequential Process 的简称,中文可以叫做通信顺序进程,

    2024年02月07日
    浏览(21)
  • GO语言网络编程(并发编程)runtime包

    1.1.1. runtime.Gosched() 让出CPU时间片,重新等待安排任务(大概意思就是本来计划的好好的周末出去烧烤,但是你妈让你去相亲,两种情况第一就是你相亲速度非常快,见面就黄不耽误你继续烧烤,第二种情况就是你相亲速度特别慢,见面就是你侬我侬的,耽误了烧烤,但是还馋就

    2024年02月09日
    浏览(39)
  • Go语言并发编程(千锋教育)

    视频地址:https://www.bilibili.com/video/BV1t541147Bc?p=14 作者B站:https://space.bilibili.com/353694001 源代码:https://github.com/rubyhan1314/go_goroutine 1.1、并发与并行 其实操作系统里对这些概念都有所说明和举例。 并发 并发是指多个任务在同一时间段内交替执行,从外部看似乎是同时执行的。

    2024年02月14日
    浏览(47)
  • Go 语言面试题(三):并发编程

    对于无缓冲的 channel,发送方将阻塞该信道,直到接收方从该信道接收到数据为止,而接收方也将阻塞该信道,直到发送方将数据发送到该信道中为止。 对于有缓存的 channel,发送方在没有空插槽(缓冲区使用完)的情况下阻塞,而接收方在信道为空的情况下阻塞。 例如: 协

    2024年02月08日
    浏览(39)
  • 云原生时代崛起的编程语言Go并发编程实战

    @ 目录 概述 基础理论 并发原语 协程-Goroutine 通道-Channel 多路复用-Select 通道使用 超时-Timeout 非阻塞通道操作 关闭通道 通道迭代 定时器-TimerAndTicker 工作池-Worker Pools 等待组-WaitGroup 原子操作-Atomic 互斥锁-Mutex 读写互斥锁-RWMutex 有状态协程 单执行-Once 条件-Cond 上下文-Context 信

    2024年02月02日
    浏览(36)
  • Go 语言并发编程 及 进阶与依赖管理

    协程可以理解为 轻量级线程 ; Go更适 合高并发场景原因 之一: Go语言 一次可以创建上万协成 ; “快速”: 开多个协成 打印。 go func() : 在 函数前加 go 代表 创建协程 ; time.Sleep() : 协程阻塞,使主协程 在 子协程结束前阻塞不退出 ; 乱序输出 说明并行 ; 通过通信共享内

    2024年02月13日
    浏览(34)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包