Golang

关注公众号 jb51net

关闭
首页 > 脚本专栏 > Golang > Go 阻塞

Go 阻塞的实现示例

作者:比猪聪明

Go语言提供了多种同步和通信机制,可以用于实现阻塞的效果,本文主要介绍了Go 阻塞的实现示例,具有一定的参考价值,感兴趣的可以了解一下

阻塞 

在Go语言中,阻塞通常指的是一个goroutine(轻量级线程)在等待另一个goroutine完成操作(如I/O操作、channel通信等)时,暂时停止执行的现象。Go语言提供了多种同步和通信机制,可以用于实现阻塞的效果。

使用 Channel 实现阻塞

Channel 是Go语言中的一个核心特性,用于在goroutines之间进行通信。通过channel,你可以实现阻塞等待数据或命令。

package main

import (
	"fmt"
	"time"
)

func main() {
	c := make(chan struct{})
	go func() {
		fmt.Println("业务处理~~~")
		time.Sleep(2 * time.Second)
		fmt.Println("业务处理完成~~~")
		close(c) // 关闭channel,通知工作完成
	}()

	<-c // 阻塞等待channel关闭
	fmt.Println("处理其他业务~~~")
}

使用 WaitGroup 实现阻塞

WaitGroup 是Go语言中用于同步一组并发操作的另一个工具。它通过计数器来跟踪完成的操作数量。

package main

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

func main() {
	var wg sync.WaitGroup //控制并发组

	doWork := func(i int) {
        // wg.Done(): 表示一个事件已经完成。它等价于 wg.Add(-1),但更明确地表达了“完成一个任务”的意图,并且在使用上更安全,因为它不会导致计数变为负数(如果已经到达零,则会panic)。
		defer wg.Done() // 当函数返回时,通知WaitGroup一个操作已完成相当于wg.Add(-1)
		fmt.Println("处理业务~~~" + strconv.Itoa(i))
		time.Sleep(2 * time.Second)
		fmt.Println("业务处理完成~~~" + strconv.Itoa(i))
	}

	for i := 0; i < 5; i++ {
		wg.Add(1)    // 增加WaitGroup的计数器
		go doWork(i) // 启动一个goroutine做工作
	}
	//主goroutine调用wg.Wait(),直到所有启动的goroutines都通过调用wg.Done()通知它们已经完成工作
	wg.Wait() // 阻塞,直到WaitGroup的计数器为0
	fmt.Println("所有业务处理完成~~~")
}

使用 Mutex 和 Conditional Variables 实现阻塞

Mutex(互斥锁)和条件变量可以用来同步访问共享资源,并实现基于条件的阻塞。 

package main

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

func main() {
	var mtx sync.Mutex         //创建互斥锁
	cond := sync.NewCond(&mtx) //使用mtx作为底层互斥锁
	ready := false

	// 启动一个 goroutine 来改变条件变量 ready 的值,并通知 cond。
	go func() {
		fmt.Println("循环跟goroutine是go内部决定先调度的--------------------goroutine--------------------")
		time.Sleep(3 * time.Second)
		mtx.Lock() //使用互斥锁
		ready = true
		cond.Signal() // 唤醒至少一个等待的 goroutine
		mtx.Unlock()  //解锁
	}()

	mtx.Lock() // 锁定互斥锁,准备进入条件等待
	for !ready {
		fmt.Println("循环跟goroutine是go内部决定先调度的--------------------阻塞--------------------")
		cond.Wait() // 阻塞,直到 cond.Signal() 被调用
		//mtx.Unlock()
	}
	mtx.Unlock() // 解锁互斥锁,继续执行(此处mtx.Unlock()在for循环里面阻塞等待完成后也可以,也可以没有,因为主线程会结束,但如果后续还需要获取互斥锁则必须要释放否则报错)

	fmt.Println("准备继续~~~")
}

这里是一些关键的修改和注意事项:

在Go语言中,sync.Mutex(互斥锁)用于保护共享资源不被多个goroutine同时修改,以避免竞态条件。sync.Cond(条件变量)与互斥锁结合使用,可以在多个goroutine之间同步共享条件。以下是关于何时使用 mtx.Lock() 和 mtx.Unlock() 的指导:

mtx.Lock()

mtx.Unlock()

注意事项

永久阻塞

Go 的运行时的当前设计,假定程序员自己负责检测何时终止一个 goroutine 以及何时终止该程序。可以通过调用 os.Exit 或从 main() 函数的返回来以正常方式终止程序。而有时候我们需要的是使程序阻塞在这一行。

使用 sync.WaitGroup 

一直等待直到 WaitGroup 等于 0 

package main

import "sync"

func main() {
	var wg sync.WaitGroup
	wg.Add(1)
	wg.Wait()
}

空 select

 select{}是一个没有任何 case 的 select,它会一直阻塞

package main

func main() {
	select{}
}

 死循环

虽然能阻塞,但会 100%占用一个 cpu。不建议使用

package main

func main() {
	for {}
}

 用 sync.Mutex

一个已经锁了的锁,再锁一次会一直阻塞,这个不建议使用

package main

import "sync"

func main() {
	var m sync.Mutex
	m.Lock()
}

 os.Signal

系统信号量,在 go 里面也是个 channel,在收到特定的消息之前一直阻塞  

package main

import (
	"os"
	"os/signal"
	"syscall"
)

func main() {
	sig := make(chan os.Signal, 2)
	//syscall.SIGTERM 是默认的终止进程信号,通常由服务管理器(如systemd、supervisor等)发送来请求程序正常终止。
	//syscall.SIGINT 是中断信号,一般由用户按下Ctrl+C键触发,用于请求程序中断执行
	signal.Notify(sig, syscall.SIGTERM, syscall.SIGINT)
	<-sig
}

从终端发送信号

 从Go代码内部发送信号

package main

import (
	"os"
	"os/signal"
	"syscall"
	"time"
)

func main() {
	sig := make(chan os.Signal, 2)
	//syscall.SIGTERM 是默认的终止进程信号,通常由服务管理器(如systemd、supervisor等)发送来请求程序正常终止。
	//syscall.SIGINT 是中断信号,一般由用户按下Ctrl+C键触发,用于请求程序中断执行
	signal.Notify(sig, syscall.SIGTERM, syscall.SIGINT)

	go func() {
		time.Sleep(10 * time.Second)
		sig <- syscall.SIGTERM
	}()

	go func() {
		time.Sleep(5 * time.Second)
		sig <- syscall.SIGINT
	}()

	<-sig
}

使用外部工具或服务管理器

如果你的Go程序作为服务运行,可能由如systemd、supervisord等服务管理器控制,这些管理器通常提供了发送信号给托管服务的机制。具体操作需参考相应服务管理器的文档。

空 channel 或者 nil channel 

channel 会一直阻塞直到收到消息,nil channel 永远阻塞。 

package main

func main() {
	c := make(chan struct{})
	<-c
}
package main

func main() {
	var c chan struct{} //nil channel
	<-c
}

 总结

 注意上面写的的代码大部分不能直接运行,都会 panic,提示“all goroutines are asleep - deadlock!”,因为 go 的 runtime 会检查你所有的 goroutine 都卡住了, 没有一个要执行。

你可以在阻塞代码前面加上一个或多个你自己业务逻辑的 goroutine,这样就不会 deadlock 了。

到此这篇关于Go 阻塞的实现示例的文章就介绍到这了,更多相关Go 阻塞内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文