go的并发编程

简介: go的并发编程

如果了解了GMP模型之后,自然了解go的并发特点,协程之间都可能是多线程并发执行的,通过开协程就可以实现并发:

package main
import (
   "fmt"
   "strconv"
   "time"
)
func main() {
   go test("1")
   go test("2")
   go test("3")
   test("main")
   time.Sleep(time.Second*10)
}
func test(name string)  {
   for i:=0;i<10;i++ {
      time.Sleep(1)
      fmt.Println(name+":  "+strconv.Itoa(i))
   }
}

输出:

image.png

要注意的是,GMP模型下,协程一定是并发的,但不一定是并行的

看代码可以看到,我额外加了一个sleep,那是因为main协程如果结束运行了,子协程也会直接结束,sleep等待子协程执行一会儿,这样才能打印出数据

这个实现方案显然不太好,我们可以通过waitGroup实现协程等待

WaitGroup

package main
import (
   "fmt"
   "strconv"
   "sync"
)
var wg sync.WaitGroup
func main() {
   for i:=1;i<=3;i++ {
      wg.Add(1)
      go test(strconv.Itoa(i))
   }
   wg.Add(1)
   test("main")
   wg.Wait()
}
func test(name string)  {
   defer wg.Done()
   for i:=0;i<10;i++ {
      time.Sleep(1)
      fmt.Println(name+":  "+strconv.Itoa(i))
   }
}

回到之前的代码,可看到我在for循环中增加了一个sleep,sleep的意义是让出时间片,从而去执行其他的协程进行并发 (GMP模型,如果没有让出时间片,同时所有协程都在同一个线程下时,协程之间将顺序执行,例如协程1运行完才会运行协程2)

主要实现了一个协程切换调度的功能

我们也可通过runtime包去做协程调度

runtime

package main
import (
   "fmt"
   "runtime"
   "strconv"
   "sync"
)
var wg sync.WaitGroup
func main() {
   //runtime.Gosched() //当前协程让出
   //runtime.Goexit() //直接退出当前协程
   //runtime.GOMAXPROCS(1) //限制P队列数量,如果为1,则无法并行
   //runtime.NumGoroutine() //返回正在执行和排队的协程数
   for i:=1;i<=3;i++ {
      wg.Add(1)
      go test(strconv.Itoa(i))
   }
   wg.Add(1)
   test("main")
   wg.Wait()
}
func test(name string)  {
   defer wg.Done()
   for i:=0;i<10;i++ {
      runtime.Gosched()
      fmt.Printf("当前协程数:%d \\n",runtime.NumGoroutine())
      fmt.Println(name+":  "+strconv.Itoa(i))
   }
}

并发问题

多开协程自然会有并发问题,我们可以通过waitGroup去控制主协程在子协程执行完之后进行操作,可以通过runtime包进行做协程并发切换,但这2个都没有涉及到变量共享问题,如何实现go的变量协程安全呢?

首先我们要理解一句话:

goroutine 奉行通过通信来共享内存,而不是共享内存来通信。

channel

通过channel,进行安全的传输变量

package main
import (
   "fmt"
   "runtime"
   "strconv"
   "sync"
)
var wg sync.WaitGroup
func main() {
   runtime.GOMAXPROCS(8)
   var chann = make(chan int)
   go func() {
      //模拟100条数据需要处理
      for i:=0;i<100;i++ {
         chann<-i
      }
      close(chann)
   }()
   //开3个协程处理
   for j := 0; j < 3; j++ {
      wg.Add(1)
      go queueHandle(strconv.Itoa(j),chann)
   }
   wg.Wait()
}
func queueHandle(name string,chann chan int)  {
   defer wg.Done()
   for i := range chann {
      fmt.Println("协程"+name+"处理数据:",i)
   }
}

可看到,3个协程通过channel,安全的获取到了需要处理的通道数据:

image.png

协程变量安全

package main
import (
   "fmt"
   "runtime"
   "sync"
   "time"
)
var a int = 0
var wg sync.WaitGroup
func main() {
   runtime.GOMAXPROCS(8)
   for i := 0; i < 10000; i++ {
      go add()
   }
   time.Sleep(time.Second * 1)
   wg.Wait()
   fmt.Println("i:", a)
}
func add() {
   defer wg.Done()
   wg.Add(1)
   a += 1
}

开启足够多的协程之后,协程变量出现了协程污染,导致最后a的值小于10000:

image.png

sync包

上面的waitGroup,其实就是sync包的一种类型,sync中还存在着其他的类型

sync.Mutex互斥锁

package main
import (
   "fmt"
   "runtime"
   "sync"
   "time"
)
var a int = 0
var wg sync.WaitGroup
var lock sync.Mutex
func main() {
   //var lock sync.Mutex
   //lock.Lock() //加锁,加锁后其他协程调用将阻塞直到解锁
   //lock.Unlock() //解锁
   runtime.GOMAXPROCS(8)
   for i := 0; i < 10000; i++ {
      go add()
   }
   time.Sleep(time.Second * 1)
   wg.Wait()
   fmt.Println("i:", a)
}
func add() {
   defer wg.Done()
   wg.Add(1)
   lock.Lock()
   defer lock.Unlock()
   a += 1
}

sync.RWMutex  读写锁

func (rw *RWMutex) Lock()
func (rw *RWMutex) Unlock()
func (rw *RWMutex) RLock()
func (rw *RWMutex) RUnlock()

rwmutex基于 mutex实现,多个协程可以重复获取读锁,如果获取写锁,其他协程读锁也将阻塞,这个读写锁太简单了,不说了

sync.Once 只执行一次

当在高并发情况下时,我们可能需要保证一个函数只执行一次,例如单例模式,加载配置文件,等等,我们可以通过sync.once实现

func (o *Once) Do(f func()) {}

单例模式实现

package main
import (
   "fmt"
   "sync"
)
type testStruct struct {}
var singleton *testStruct
var  once =  sync.Once{}
func GetInstance()*testStruct  {
   once.Do(func() {
      singleton = &testStruct{}
      fmt.Println("执行实例化")
   })
   return singleton
}

sync.once内部存在一个mutex锁和一个bool值,如果bool为false,则通过mutex加锁执行一次,然后bool为true直接忽略执行

协程安全类型

代码中的加锁操作因为涉及内核态的上下文切换会比较耗时、代价比较高。针对基本数据类型我们还可以使用原子操作来保证并发安全

协程安全的变量类型有sync.map,atomic包等

太简单了,不讲了

目录
相关文章
|
3月前
|
数据采集 安全 Go
Go 语言并发编程基础:Goroutine 的创建与调度
Go 语言的 Goroutine 是轻量级线程,由 runtime 管理,具有启动快、占用小、支持高并发的特点。本章介绍 Goroutine 的基本概念、创建方式(如使用 `go` 关键字或匿名函数)、M:N 调度模型及其工作流程,并探讨其在高并发场景中的应用,帮助理解其高效并发的优势。
|
3月前
|
Go 开发者
Go 并发编程基础:无缓冲与有缓冲通道
本章深入探讨Go语言中通道(Channel)的两种类型:无缓冲通道与有缓冲通道。无缓冲通道要求发送和接收必须同步配对,适用于精确同步和信号通知;有缓冲通道通过内部队列实现异步通信,适合高吞吐量和生产者-消费者模型。文章通过示例对比两者的行为差异,并分析死锁风险及使用原则,帮助开发者根据场景选择合适的通道类型以实现高效并发编程。
|
9月前
|
并行计算 安全 Go
Go语言中的并发编程:掌握goroutines和channels####
本文深入探讨了Go语言中并发编程的核心概念——goroutine和channel。不同于传统的线程模型,Go通过轻量级的goroutine和通信机制channel,实现了高效的并发处理。我们将从基础概念开始,逐步深入到实际应用案例,揭示如何在Go语言中优雅地实现并发控制和数据同步。 ####
|
10月前
|
存储 Go 开发者
Go语言中的并发编程与通道(Channel)的深度探索
本文旨在深入探讨Go语言中并发编程的核心概念和实践,特别是通道(Channel)的使用。通过分析Goroutines和Channels的基本工作原理,我们将了解如何在Go语言中高效地实现并行任务处理。本文不仅介绍了基础语法和用法,还深入讨论了高级特性如缓冲通道、选择性接收以及超时控制等,旨在为读者提供一个全面的并发编程视角。
202 50
|
10月前
|
安全 Serverless Go
Go语言中的并发编程:深入理解与实践####
本文旨在为读者提供一个关于Go语言并发编程的全面指南。我们将从并发的基本概念讲起,逐步深入到Go语言特有的goroutine和channel机制,探讨它们如何简化多线程编程的复杂性。通过实例演示和代码分析,本文将揭示Go语言在处理并发任务时的优势,以及如何在实际项目中高效利用这些特性来提升性能和响应速度。无论你是Go语言的初学者还是有一定经验的开发者,本文都将为你提供有价值的见解和实用的技巧。 ####
|
10月前
|
算法 安全 程序员
Go语言的并发编程:深入理解与实践####
本文旨在探讨Go语言在并发编程方面的独特优势及其实现机制,通过实例解析关键概念如goroutine和channel,帮助开发者更高效地利用Go进行高性能软件开发。不同于传统的摘要概述,本文将以一个简短的故事开头,引出并发编程的重要性,随后详细阐述Go语言如何简化复杂并发任务的处理,最后通过实际案例展示其强大功能。 --- ###
|
10月前
|
Go 开发者
Go语言中的并发编程:掌握goroutines和channels####
本文深入探讨了Go语言中并发编程的核心概念,重点介绍了goroutines和channels的工作原理及其在实际开发中的应用。文章通过实例演示如何有效地利用这些工具来编写高效、可维护的并发程序,旨在帮助读者理解并掌握Go语言在处理并发任务时的强大能力。 ####
|
10月前
|
安全 Go 数据处理
Go语言中的并发编程:掌握goroutine和channel的艺术####
本文深入探讨了Go语言在并发编程领域的核心概念——goroutine与channel。不同于传统的单线程执行模式,Go通过轻量级的goroutine实现了高效的并发处理,而channel作为goroutines之间通信的桥梁,确保了数据传递的安全性与高效性。文章首先简述了goroutine的基本特性及其创建方法,随后详细解析了channel的类型、操作以及它们如何协同工作以构建健壮的并发应用。此外,还介绍了select语句在多路复用中的应用,以及如何利用WaitGroup等待一组goroutine完成。最后,通过一个实际案例展示了如何在Go中设计并实现一个简单的并发程序,旨在帮助读者理解并掌
|
10月前
|
安全 Java Go
Go语言中的并发编程:掌握goroutine与通道的艺术####
本文深入探讨了Go语言中的核心特性——并发编程,通过实例解析goroutine和通道的高效使用技巧,旨在帮助开发者提升多线程程序的性能与可靠性。 ####
|
10月前
|
Go 调度 开发者
Go语言中的并发编程:深入理解goroutines和channels####
本文旨在探讨Go语言中并发编程的核心概念——goroutines和channels。通过分析它们的工作原理、使用场景以及最佳实践,帮助开发者更好地理解和运用这两种强大的工具来构建高效、可扩展的应用程序。文章还将涵盖一些常见的陷阱和解决方案,以确保在实际应用中能够避免潜在的问题。 ####