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())
}
相关推荐
littleschemer1 小时前
Go缓存系统
缓存·go·cache·bigcache
程序者王大川1 天前
【GO开发】MacOS上搭建GO的基础环境-Hello World
开发语言·后端·macos·golang·go
Grassto1 天前
Gitlab 中几种不同的认证机制(Access Tokens,SSH Keys,Deploy Tokens,Deploy Keys)
go·ssh·gitlab·ci
高兴的才哥2 天前
kubevpn 教程
kubernetes·go·开发工具·telepresence·bridge to k8s
少林码僧2 天前
sqlx1.3.4版本的问题
go
蒙娜丽宁3 天前
Go语言结构体和元组全面解析
开发语言·后端·golang·go
蒙娜丽宁3 天前
深入解析Go语言的类型方法、接口与反射
java·开发语言·golang·go
三里清风_3 天前
Docker概述
运维·docker·容器·go
蒙娜丽宁3 天前
深入探讨Go语言中的切片与数组操作
开发语言·后端·golang·go
qq_172805594 天前
GO HTTP库使用
http·golang·go