Go并发编程

并发编程

基础

  • 进程(process):进程是并发执行的程序中分配和管理资源的基本单位。
  • 线程(thread):线程是进程的执行单元,是进行调度的实体,是比进程更小的独立运行单位。
  • 并发(concurrent):多线程交替操作同一资源。
  • 并行(parallel):多个线程同时操作多个资源。

协程goroutine

协程的概念

协程是单线程下的并发,又称为微线程。它是实现多任务的另一种方式,只不过是比线程更小的执行单元。因为它自带CPU的上下文,这样只要在合适时机,我们都可以将一个协程切换到另一个协程。英文名Coroutine。

轻量级的线程,独立的栈空间,共享程序堆空间,调度由用户控制,是逻辑态,对资源消耗小。

线程和协程的区别?

线程的切换是一个CPU在不同线程中来回切换,是从系统层面来,不止保存和恢复CPU上下文这么简单,会非常耗费性能。但是协程只是在同一个线程内来回切换不同的函数,只是简单的操作CPU的上下文,所以耗费的性能会大大减小。

多线程并发资源竞争的问题

解决方案一:互斥锁

go 复制代码
package main

import (
	"fmt"
	"sync"
	"time"
)

var counter int
var lock sync.Mutex

func increment() {
	lock.Lock()
	for i := 0; i < 100000; i++ {
		counter++
	}
	lock.Unlock()
}

func main() {
	for i := 0; i < 20; i++ {
		go increment()
	}
	time.Sleep(3 * time.Second)
	fmt.Printf("Final counter value: %d\n", counter)
}

解决方案二:channel

chan 本质就是一个数据结构-队列。

先进先出FIFO的规则,线程安全,多goroutine访问不需要加锁,因为通道本身线程安全。

注意:channel是有类型的,定义存放的类型不能放不同的类型。如果是空接口就能放所有类型。

go 复制代码
package main

import (
	"fmt"
	"time"
)

var counter int
var incrementChan = make(chan struct{}, 1)

func increment() {
	for i := 0; i < 100000; i++ {
		incrementChan <- struct{}{}
		counter++
		<-incrementChan
	}
}

func main() {
	for i := 0; i < 20; i++ {
		go increment()
	}
	time.Sleep(3 * time.Second)
	fmt.Printf("Final counter value: %d\n", counter)
}

goroutine和channel的综合应用

go 复制代码
package main

import (
    "fmt"
    "math/rand"
    "time"
)

var intChan chan int

func WriterData(intCh chan int) {
    rand.Seed(time.Now().UnixNano())
    for i := 1; i < 150; i++ {
       var temp int
       temp = rand.Intn(4) + 10
       intCh <- temp
       fmt.Println("write data:", temp)
    }
    defer close(intCh)
}

func ReadData(intCh chan int, exitChan chan bool) {
    var count int
    for {
       data, ok := <-intCh
       if !ok {
          break
       }
       count++
       fmt.Println("count:", count, "data:", data)
    }
    exitChan <- true
    defer close(exitChan)
}

func main() {
    intChan = make(chan int, 50)
    exitChan := make(chan bool, 1)
    go WriterData(intChan)
    go ReadData(intChan, exitChan)
    time.Sleep(time.Second * 1)
    fmt.Println("main exit")
}

defer匿名函数捕获Panic

多个协程可能会有panic导致整个程序崩溃。

go 复制代码
package main

import (
	"fmt"
	"sync"
)

func recoverFromPanic() {
	if r := recover(); r != nil {
		fmt.Println("Recovered from panic:", r)
	}
}

func doWork(id int, wg *sync.WaitGroup) {
	defer wg.Done()
	defer recoverFromPanic()
	fmt.Printf("Worker %d started\n", id)
	if id == 2 {
		panic("Something went wrong!")
	}
	fmt.Printf("Worker %d finished\n", id)
}

func main() {
	var wg sync.WaitGroup
	for i := 1; i <= 5; i++ {
		wg.Add(1)
		go doWork(i, &wg)
	}
	wg.Wait()
	fmt.Println("All workers finished")
}

生产者消费者模式

go 复制代码
package main

import (
	"fmt"
	"time"
)

// 模拟订单对象
type OrderInfo struct {
	id int
}

// 生产订单--生产者
func producerOrder(out chan<- OrderInfo) {
	for i := 0; i < 10; i++ {
		order := OrderInfo{id: i + 1}
		fmt.Println("生成订单,订单ID为:", order.id)
		out <- order
	}
	close(out)
}

// 处理订单--消费者
func consumerOrder(in <-chan OrderInfo) {
	for order := range in {
		fmt.Println("读取订单,订单ID为:", order.id)
	}
}

func main() {
	ch := make(chan OrderInfo, 5)
	go producerOrder(ch)
	go consumerOrder(ch)
	time.Sleep(time.Second * 2)
}

协程管道定时任务的应用

1.定时执行某个任务,类似延时消息队列

2.周期性的执行某个任务,类似定期同步某些数据

go 复制代码
func main() {
	fmt.Println("当前时间:", time.Now())
	//timer := time.NewTimer(time.Second * 2)
	//t := timer.C
	t := <-time.After(time.Second * 2)
	fmt.Println(t)
}

定时器的停止与重置

timer.Stop()

go 复制代码
package main

import (
	"fmt"
	"time"
)

func testChannelTimeout(conn chan int) bool {
	// 设置 1 秒的定时器,若在到了1 s ,则进行打印,说明已经超时
	timer := time.NewTimer(1 * time.Second)

	select {
	case <-conn:
		if timer.Stop() {
			fmt.Println("timer.Stop()")
		}
		return true
	case <-timer.C: // timer 通道超时
		fmt.Println("timer Channel timeout!")
		return false
	}
}

func main() {

	ch := make(chan int, 1)
	// 若打开如下语句,则可以正常关闭定时器
	// 若注释如下语句,则关闭定时器超时
	ch <- 1
	go testChannelTimeout(ch)

	for {
	}
}

timer.Reset()

go 复制代码
package main

import (
	"fmt"
	"time"
)

func main() {
	fmt.Println("start", time.Now())
	myt := time.NewTimer(time.Second * 2)
	// 重置为5秒
	myt.Reset(time.Second * 5)
	<-myt.C
	fmt.Println("end", time.Now())
}
相关推荐
我是前端小学生7 小时前
Go语言中的方法和函数
go
探索云原生12 小时前
在 K8S 中创建 Pod 是如何使用到 GPU 的: nvidia device plugin 源码分析
ai·云原生·kubernetes·go·gpu
自在的LEE18 小时前
当 Go 遇上 Windows:15.625ms 的时间更新困局
后端·kubernetes·go
Gvto2 天前
使用FakeSMTP创建本地SMTP服务器接收邮件具体实现。
go·smtp·mailtrap
白泽来了2 天前
【Go进阶】手写 Go websocket 库(一)|WebSocket 通信协议
开源·go
witton2 天前
将VSCode配置成Goland的视觉效果
ide·vscode·编辑器·go·字体·c/c++·goland
非凡的世界2 天前
5个用于构建Web应用程序的Go Web框架
golang·go·框架·web
湫qiu2 天前
6.5840 Lab-Key/Value Server 思路
后端·go
我是前端小学生3 天前
Go语言中的init函数
go
我是前端小学生3 天前
Go语言中内部模块的可见性规则
go