泊头市网站制作公司,关键词排名网站,免费版个人简历,文件包上传的网站怎么做1. 进程、线程和协程的区别 进程: 进程是具有一定独立功能的程序#xff0c;进程是系统资源分配和调度的最小单位。每个进程都有自己的独立内存空间#xff0c;不同进程通过进程间通信来通信。由于进程比较重量#xff0c;占据独立的内存#xff0c;所以上下文进程间的切换…1. 进程、线程和协程的区别 进程: 进程是具有一定独立功能的程序进程是系统资源分配和调度的最小单位。每个进程都有自己的独立内存空间不同进程通过进程间通信来通信。由于进程比较重量占据独立的内存所以上下文进程间的切换开销栈、寄存器、虚拟内存、文件句柄等比较大但相对比较稳定安全。
进程就是应用程序的启动实例。比如我们运行一个游戏打开一个软件就是开启了一个进程。 线程: 线程是进程的一个实体线程是内核态而且是 CPU 调度和分派的基本单位它是比进程更小的能独立运行的基本单位。线程间通信主要通过共享内存上下文切换很快资源开销较少但相比进程不够稳定容易丢失数据。
线程从属于进程是程序的实际执行者。一个进程至少包含一个主线程也可以有更多的子线程。 协程: 协程是一种用户态的轻量级线程协程的调度完全是由用户来控制的。协程拥有自己的寄存器上下文和栈。协程调度切换时将寄存器上下文和栈保存到其他地方在切回来的时候恢复先前保存的寄存器上下文和栈直接操作栈则基本没有内核切换的开销可以不加锁的访问全局变量所以上下文的切换非常快。
协程是一种比线程更加轻量级的存在。正如一个进程可以拥有多个线程一样一个线程也可以拥有多个协程。最重要的是协程不是被操作系统内核所管理而完全是由程序所控制也就是在用户态中执行。 线程和协程的区别 线程切换需要陷入内核然后进行上下文切换而协程在用户态由协程调度器完成不需要陷入内核这样代价就小了。协程的切换时间点是由调度器决定而不是由系统内核决定的尽管它们的切换点都是时间片超过一定阈值或者是进入 I/O 或睡眠等状态时。基于垃圾回收的考虑Go 实现了垃圾回收但垃圾回收的必要条件是内存位于一致状态因此就需要暂停所有的线程。如果交给系统去做那么会暂停所有的线程使其一致。对于 Go 语言来说调度器知道什么时候内存位于一致状态所以也就没有必要暂停所有运行的线程。
2. 介绍一下 Goroutine
Goroutine 是一个与其他 goroutines 并行运行在同一地址空间的 Go 函数或方法。
协程goroutine 轻量级线程 goroutine 是由 Go 的运行时runtime调度和管理的。Go 程序会智能地将 goroutine 中的任务合理地分配给每个 CPU。它在语言层面已经内置了调度和上下文切换的机制。
goroutine 是 Go 并发设计的核心也叫协程它比线程更加轻量因此可以同时运行成千上万个并发任务。在 Go 语言中每一个并发的执行单元叫作一个 goroutine。我们只需要在调用的函数前面添加 go 关键字就能使这个函数以协程的方式运行。
3. context 包结构原理和用途
Context上下文是 Golang 应用开发常用的并发控制技术 它可以控制一组呈树状结构的 goroutine每个 goroutine 拥有相同的上下文。Context 是并发安全的主要是用于控制多个协程之间的协作、取消操作。
Context 只定义了接口凡是实现该接口的类都可称为是一种 context。contex理分析
「Deadline」 方法可以获取设置的截止时间返回值 deadline 是截止时间到了这个时间Context 会自动发起取消请求返回值 ok 表示是否设置了截止时间。「Done」 方法返回一个只读的 channel 类型为 struct {}。如果这个 chan 可以读取说明已经发出了取消信号可以做清理操作然后退出协程释放资源。「Err」 方法返回 Context 被取消的原因。「Value」 方法获取 Context 上绑定的值是一个键值对通过 key 来获取对应的值。 4. goroutine 调度
GMP 是 Go 语言运行时runtime层面的实现是 go 语言自己实现的一套调度系统。区别于操作系统调度 OS 线程。
G 很好理解就是个 goroutine 的里面除了存放本 goroutine 信息外 还有与所在 P 的绑定等信息。P 管理着一组 goroutine 队列P 里面会存储当前 goroutine 运行的上下文环境函数指针堆栈地址及地址边界P 会对自己管理的 goroutine 队列做一些调度比如把占用 CPU 时间较长的 goroutine 暂停、运行后续的 goroutine 等等当自己的队列消费完了就去全局队列里取如果全局队列里也消费完了会去其他 P 的队列里抢任务。 Mmachine是 Go 运行时runtime对操作系统内核线程的虚拟 M 与内核线程一般是一一映射的关系 一个 groutine 最终是要放到 M 上执行的 P 与 M 一般也是一一对应的。他们关系是 P 管理着一组 G 挂载在 M 上运行。当一个 G 长久阻塞在一个 M 上时runtime 会新建一个 M阻塞 G 所在的 P 会把其他的 G 挂载在新建的 M 上。当旧的 G 阻塞完成或者认为其已经死掉时 回收旧的 M。 P 的个数是通过 runtime.GOMAXPROCS 设定最大 256Go1.5 版本之后默认为物理线程数。 在并发量大的时候会增加一些 P 和 M但不会太多切换太频繁的话得不偿失。 单从线程调度讲Go 语言相比起其他语言的优势在于 OS 线程是由 OS 内核来调度的goroutine 则是由 Go 运行时runtime自己的调度器调度的这个调度器使用一个称为 m:n 调度的技术复用 / 调度 m 个 goroutine 到 n 个 OS 线程。 其一大特点是 goroutine 的调度是在用户态下完成的 不涉及内核态与用户态之间的频繁切换包括内存的分配与释放都是在用户态维护着一块大的内存池 不直接调用系统的 malloc 函数除非内存池需要改变成本比调度 OS 线程低很多。 另一方面充分利用了多核的硬件资源近似的把若干 goroutine 均分在物理线程上 再加上本身 goroutine 的超轻量以上种种保证了 go 调度方面的性能。 5. 如何避免 Goroutine 泄露和泄露场景
gorouinte 里有关于 channel 的操作如果没有正确处理 channel 的读取会导致 channel 一直阻塞住goroutine 不能正常结束
channel 操作不当:
package mainimport (fmttime
)func main() {// 问题场景: 在 Goroutine 中创建 channel,但没有相应的 channel 读取操作go func() {ch : make(chan int)ch - 1 // 这里会导致 Goroutine 永久阻塞}()time.Sleep(5 * time.Second)fmt.Println(Program ended)
}在上面的代码中,我们创建了一个 Goroutine,它向一个无人接收的 channel 写入数据。这会导致该 Goroutine 永久阻塞,从而造成 Goroutine 泄露。
解决方法如下:
package mainimport (fmttime
)func main() {// 解决方法: 确保 channel 的读取操作与写入操作相匹配ch : make(chan int)go func() {x : -ch // 从 channel 中读取数据fmt.Println(Received value:, x)}()ch - 1 // 向 channel 中写入数据time.Sleep(5 * time.Second)fmt.Println(Program ended)
}在这个解决方案中,我们在创建 Goroutine 之前先创建了 channel,并在 Goroutine 中读取 channel 中的数据。这样可以确保 Goroutine 能够正常退出,避免泄露。
context 使用不当:
package mainimport (contextfmttime
)func main() {// 错误示例: 在 Goroutine 中使用 context,但没有正确地取消 contextctx, _ : context.WithTimeout(context.Background(), 2*time.Second)go func() {doSomething(ctx)}()time.Sleep(5 * time.Second)fmt.Println(Program ended)
}func doSomething(ctx context.Context) {for {// 不检查 ctx.Done() 就一直执行time.Sleep(time.Second)fmt.Println(Doing something...)}
}在上面的代码中,我们创建了一个 Goroutine,并向其传递了一个 context。但是,我们没有在 doSomething 函数中正确地处理 context 的取消。这意味着,即使 context 已经超时,Goroutine 仍然会一直执行下去,而不会退出。,从而造成 Goroutine 泄露。
解决方法如下:
package mainimport (contextfmttime
)func main() {// 解决方法: 在 Goroutine 中正确地处理 context 的取消ctx, cancel : context.WithTimeout(context.Background(), 2*time.Second)defer cancel()go func() {doSomething(ctx)}()time.Sleep(5 * time.Second)fmt.Println(Program ended)
}func doSomething(ctx context.Context) {for {select {case -ctx.Done():fmt.Println(Context canceled, exiting Goroutine)returndefault:// 执行一些操作time.Sleep(time.Second)fmt.Println(Doing something...)}}
}在解决方案中,我们在 doSomething 函数中使用 select 语句来检查 context 是否已被取消。如果 context 已被取消,Goroutine 就会安全地退出,避免 Goroutine 泄露。
6. waitgroup 用法和原理
waitgroup 内部维护了一个计数器当调用 wg.Add(1) 方法时就会增加对应的数量当调用 wg.Done() 时计数器就会减一。直到计数器的数量减到 0 时就会调用 runtime_Semrelease 唤起之前因为 wg.Wait() 而阻塞住的 goroutine。
使用方法
main 协程通过调用 wg.Add (delta int) 设置 worker 协程的个数然后创建 worker 协程worker 协程执行结束以后都要调用 wg.Done () main 协程调用 wg.Wait () 且被 block直到所有 worker 协程全部执行结束后返回。 实现原理 WaitGroup 主要维护了 2 个计数器一个是请求计数器 v一个是等待计数器 w二者组成一个 64bit 的值请求计数器占高 32bit等待计数器占低 32bit。 每次 Add 执行请求计数器 v 加 1Done 方法执行请求计数器减 1v 为 0 时通过信号量唤醒 Wait ()。