GoLang并发编程

1. 并发

进程、线程
进程是拥有资源和独立运行的最小单位,也是操作系统分配资源的最小单位。
线程是程序执行的最小单位。
并发、并行
并发是指两个或多个事件在同一时间间隔发生,分时执行。
并行是指两个或者多个事件在同一时刻发生,同时执行。
协程、线程
协程:独立的栈空间,共享堆空间,调度由用户自己控制,本质上有点类似于用户级线程,这些用户级线程的调度也是自己实现的。
线程:一个线程上可以跑多个协程,协程是轻量级的线程。

2. Goroutine

​ goroutine是go官方实现的超级“线程池”。每个实例4~5KB的栈内存占用和用于实现机制而大幅减少的创建和销毁开销是go高并发的根本原因。

goroutine调度

GPM是Go语言运行时(runtime)层面的实现,是go语言自己实现的一套调度系统。区别于操作系统调度OS线程。

  • 1.G很好理解,就是个goroutine的,里面除了存放本goroutine信息外 还有与所在P的绑定等信息。
  • 2.P管理着一组goroutine队列,P里面会存储当前goroutine运行的上下文环境(函数指针,堆栈地址及地址边界),P会对自己管理的goroutine队列做一些调度(比如把占用CPU时间较长的goroutine暂停、运行后续的goroutine等等)当自己的队列消费完了就去全局队列里取,如果全局队列里也消费完了会去其他P的队列里抢任务。
  • 3.M(machine)是Go运行时(runtime)对操作系统内核线程的虚拟, M与内核线程一般是一一映射的关系, 一个groutine最终是要放到M上执行的;

P与M一般也是一一对应的。他们关系是: P管理着一组G挂载在M上运行。当一个G长久阻塞在一个M上时,runtime会新建一个M,阻塞G所在的P会把其他的G 挂载在新建的M上。当旧的G阻塞完成或者认为其已经死掉时 回收旧的M。

goroutine池

​ 有效的控制goroutine数量,防止暴涨

3. runtime

runtime.Gosched() 当前goroutine让出CPU,好让其它的goroutine获得执行的机会。同时,当前的goroutine也会在未来的某个时间点继续运行。
runtime.Goexit() 终止调用它的Goroutine的运行之前会先执行该Groution中还没有执行的defer语句。
runtime.GOMAXPROCS 定需要使用多少个OS线程来同时执行Go代码。默认值是机器上的CPU核心数。Go语言中可以通过runtime.GOMAXPROCS()函数设置当前程序并发时占用的CPU逻辑核心数。

4. Channel

​ chanel是goroutine之间连接的通道,可以将一个goroutine发送特定值到另一个goroutine的通信机制。

var ch chan int		// 通道声明格式
make(chan 元素类型,[缓冲大小])		// 创建通道格式
ch <- 10	// 把10发送到通道
x := <- ch	// 从通道接收值
close(ch)	// 关闭通道(通道可以被垃圾回收机制回收,不必一定要关闭)

关闭通道后的特点:

	1.对一个关闭的通道再发送值就会导致panic2.对一个关闭的通道进行接收会一直获取值直到通道为空。
    3.对一个关闭的并且没有值的通道执行接收操作会得到对应类型的零值。
    4.关闭一个已经关闭的通道会导致panic

无缓冲的通道要先接收(接收会先阻塞),再发送,否则会报错。

有缓冲的通道不需要先接收,在缓存满了以后会进行等待。

5. 定时器

​ Go语言在time包中提供三种定时器的使用方式:

Ticker
ticker := time.NewTicker(time.Second * 1) // 运行时长
    ch := make(chan int)
    go func() {
        var x int
        for x < 10 {
            select {
            case <-ticker.C:
                x++
                fmt.Printf("%d\n", x)
            }
        }
        ticker.Stop()
        ch <- 0
    }()
    <-ch                       // 通过通道阻塞,让任务可以执行完指定的次数。

通过 time.NewTicker() 创建,这种类型,ticker会不断的按照设定的间隔时间触发,除非主动终止运行。

Timer
timer := time.NewTimer(time.Second * 1) // timer 只能按时触发一次,可通过Reset()重置后继续触发。
    go func() {
        var x int
        for {
            select {
            case <-timer.C:
                x++
                fmt.Printf("%d,%s\n", x, time.Now().Format("2006-01-02 15:04:05"))
                if x < 10 {
                    timer.Reset(time.Second * 2)
                } else {
                    ch <- x
                }
            }
        }
    }()
    <-ch

通过 time.NewTimer() 创建,这种类型,timer只会执行一次,当然,可以在执行完以后通过调用 timer.Reset() 让定时器再次工作,并可以更改时间间隔。

After
// 阻塞一下,等待主进程结束
    tt := time.NewTimer(time.Second * 10)
    <-tt.C
    fmt.Println("over.")

    <-time.After(time.Second * 4)
    fmt.Println("再等待4秒退出。tt 没有终止,打印出 over 后会看见在继续执行...")
    tt.Stop()
    <-time.After(time.Second * 2)
    fmt.Println("tt.Stop()后, tt 仍继续执行,只是关闭了 tt.C 通道。")

After()其实是Timer的一个语法糖。

6. select

​ select的使用类似于switch语句,它有一系列case分支和一个默认的分支。每个case会对应一个通道的通信(接收或发送)过程。select会一直等待,直到某个case的通信操作完成时,就会执行case分支对应的语句。具体格式如下:

 	select {
    case <-chan1:
       // 如果chan1成功读到数据,则进行该case处理语句
    case chan2 <- 1:
       // 如果成功向chan2写入数据,则进行该case处理语句
    default:
       // 如果上面都没有成功,则进入default处理流程
    }

7. 并发安全和锁

互斥锁:并发编程中对共享资源进行访问的控制手段,由标准库中的Mutex结构体类型,
sync.Mutex类型只有两个公开的指针方法,Lock和Unlock。

​ 可以通过go build -race main.go 编译后运行, 查看共享资源的竞争。

​ 互斥锁本质是当一个goroutine访问的时候,其他的goroutin都不能访问,
​ 程序由原来的并行执行变成了串行执行。

读写锁:读写锁可以让多个读操作并发,同时读取,但是对于写操作是完全互斥的。
当一个goroutine进行写操作的时候,其他的goroutine不能读也不能写。

var x int64
var wg sync.WaitGroup	// 计数器
var lock sync.Mutex // 互斥锁

func add()  {
    for i :=0;i<1000;i++{
        lock.Lock()     // 加锁
        x = x + 1
        lock.Unlock()   // 解锁
    }
    wg.Done()	
}
func main()  {
    wg.Add(2)
    go add()
    go add()
    wg.Wait()
    fmt.Println(x)
}

8. Sync

sync.WaitGroup实现go并发任务的同步。

方法名功能
(wg * WaitGroup) Add(delta int)计数器+delta
(wg *WaitGroup) Done()计数器-1
(wg *WaitGroup) Wait()阻塞直到计数器变为0

sync.Once 是 Golang package 中使方法只执行一次的对象实现,作用与 init 函数类似。但也有所不同。

  • init 函数是在文件包首次被加载的时候执行,且只执行一次
  • sync.Onc 是在代码运行中需要的时候执行,且只执行一次

sync.Map开箱即用,内置Store、Load、LoadOrStore、Delete、Range等操作方法。

9. 原子操作(atomic)

Go语言中原子操作由内置的标准库sync/atomic提供。

方法解释
func LoadInt32(addr *int32) (val int32) func LoadInt64(addr *int64) (val int64)
func LoadUint32(addr*uint32) (val uint32)
func LoadUint64(addr*uint64) (val uint64)
func LoadUintptr(addr*uintptr) (val uintptr)
func LoadPointer(addr*unsafe.Pointer) (val unsafe.Pointer)
读取操作
func StoreInt32(addr *int32, val int32) func StoreInt64(addr *int64, val int64) func StoreUint32(addr *uint32, val uint32) func StoreUint64(addr *uint64, val uint64) func StoreUintptr(addr *uintptr, val uintptr) func StorePointer(addr *unsafe.Pointer, val unsafe.Pointer)写入操作
func AddInt32(addr *int32, delta int32) (new int32) func AddInt64(addr *int64, delta int64) (new int64) func AddUint32(addr *uint32, delta uint32) (new uint32) func AddUint64(addr *uint64, delta uint64) (new uint64) func AddUintptr(addr *uintptr, delta uintptr) (new uintptr)修改操作
func SwapInt32(addr *int32, new int32) (old int32) func SwapInt64(addr *int64, new int64) (old int64) func SwapUint32(addr *uint32, new uint32) (old uint32) func SwapUint64(addr *uint64, new uint64) (old uint64) func SwapUintptr(addr *uintptr, new uintptr) (old uintptr) func SwapPointer(addr *unsafe.Pointer, new unsafe.Pointer) (old unsafe.Pointer)交换操作
func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool) func CompareAndSwapInt64(addr *int64, old, new int64) (swapped bool) func CompareAndSwapUint32(addr *uint32, old, new uint32) (swapped bool) func CompareAndSwapUint64(addr *uint64, old, new uint64) (swapped bool) func CompareAndSwapUintptr(addr *uintptr, old, new uintptr) (swapped bool) func CompareAndSwapPointer(addr *unsafe.Pointer, old, new unsafe.Pointer) (swapped bool)比较并交换操作

10. GMP原理与调度

​ M结构是Machine,系统线程,它由操作系统管理,goroutine就是跑在M之上的;M是一个很大的结构,里面维护小对象内存cache(mcache)、当前执行的goroutine、随机数发生器等等非常多的信息
​ P结构是Processor,处理器,它的主要用途就是用来执行goroutine,它维护了一个 goroutine队列,即runqueue。Processor的让我们从N:1调度到M:N调度的重要部分。
​ G是goroutine实现的核心结构,它包含了栈,指令指针,以及其他对调度goroutine很重要的信息,例如其阻塞的channel。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值