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())
}
相关推荐
BlockChain8885 小时前
Solidity 实战【三】:重入攻击与防御(从 0 到 1 看懂 DAO 事件)
go·区块链
剩下了什么10 小时前
Gf命令行工具下载
go
地球没有花11 小时前
tw引发的对redis的深入了解
数据库·redis·缓存·go
BlockChain8881 天前
字符串最后一个单词的长度
算法·go
龙井茶Sky1 天前
通过higress AI统计插件学gjson表达式的分享
go·gjson·higress插件
宇宙帅猴2 天前
【Ubuntu踩坑及解决方案(一)】
linux·运维·ubuntu·go
SomeBottle3 天前
【小记】解决校园网中不同单播互通子网间 LocalSend 的发现问题
计算机网络·go·网络编程·学习笔记·计算机基础
且去填词3 天前
深入理解 GMP 模型:Go 高并发的基石
开发语言·后端·学习·算法·面试·golang·go
大厂技术总监下海3 天前
向量数据库“卷”向何方?从Milvus看“全功能、企业级”的未来
数据库·分布式·go·milvus·增强现实
冷冷的菜哥3 天前
go(golang)调用ffmpeg对视频进行截图、截取、增加水印
后端·golang·ffmpeg·go·音视频·水印截取截图