用户级线程:
用户级线程是通过运行在用户态的运行时库来管理的,其优点是,线程的一切(包括调度、创建)都可以完全由用户自己决定,所以具有较高的灵活性。而且由于是在用户态上进行管理,所以就省去了内核管理的开销,所以具有高效率。 但是用户级线程有一个致命的缺点:一个进程内的某一个线程阻塞将导致整个进程内的所有线程全部阻塞。而且由于用户级线程没有时间片概念,所以每个线程必须运行一段时间后将CPU让个其他的线程使用,否则,该线程将独占CPU。
内核级线程:
内核级线程是操作系统内核实现、管理和调度的一种线程。由于有操作系统管理,所以操作系统是知道线程的存在,并为其安排时间片,管理与其有关的内核对象。因为内核级线程是由内核来管理,所以每次线程创建、切换都要执行一个模式切换例程,所以内核级线程效率比较低,而且内核级线程的调度是由操作系统的设计者来决定的,所以缺乏灵活性。但是内核级线程有一个有点就是当一个进程的某个线程因为一个系统调用或者缺页中断而阻塞时,不会导致该进程的所有线程阻塞。
两级线程:
在一些系统中,使用组合方式的多线程实现, 线程创建完全在用户空间中完成,线程的调度和同步也在应用程序中进行。一个应用程序中的多个用户级线程被映射到一些(小于或等于用户级线程的数目)内核级线程上。
总结:
很多人认为goroutine比线程运行得更快,这是一个误解。Goroutine并不会更快,它只是增加了更多的并发性。当一个goroutine被阻塞(比如等待IO),golang的scheduler会调度其它可以执行的goroutine运行。与线程相比,它有以下几个优点:
开销小:
goroutine所需要的内存通常只有2kbg,而线程则需要1Mb(500倍)。
调度快:
由于线程创建时需要向操作系统申请资源,并且在销毁时将资源归还,因此它的创建和销毁的开销比较大。相比之下,goroutine的创建和销毁是由go语言在运行时自己管理的,因此开销更低。goroutine的调度是协同式的,它不会直接地与操作系统内核打交道。当goroutine进行切换的时候,之后很少量的寄存器需要保存和恢复(PC和SP)。因此gouroutine的切换效率更高。
GC回收:
GC会周期性地将不再使用的内存回收,收缩栈空间。
三色标记法
例如,当前内存中有A~F一共6个对象,根对象a,b本身为栈上分配的局部变量,根对象a、b分别引用了对象A、B, 而B对象又引用了对象D,则GC开始前各对象的状态如下图所示:
Go调度器工作时会维护两种用来保存G的任务队列:一种是一个Global任务队列,一种是每个P维护的Local任务队列。当通过go关键字创建一个新的goroutine的时候,它会优先被放入P的本地队列。为了运行goroutine,M需要持有(绑定)一个P,接着M会启动一个OS线程,循环从P的本地队列里取出一个goroutine并执行。当M执行完了当前PLocal队列里的所有G后,它会先尝试从Global队列寻找G来执行,如果Global队列为空,它会随机挑选另外一个P,从它的队列里中拿走一半的G到自己的队列中执行。
既然Go调度器这么优秀,我们为什么还要自己去实现一个golang的 Goroutine Pool 呢?有基于G-P-M的Go调度器,go程序的并发编程中,可以任性地起大规模的goroutine来执行任务,官方也宣称用golang写并发程序的时候随便起个成千上万的goroutine毫无压力。然而,起1000个goroutine没有问题,10000也没有问题,10w个可能也没问题;那,100w个呢?1000w个呢?
对象优化:
变量优化:
数据压缩:
关于字符串的使用建议:
字符串拼接的三种方式:
1.fmt.Sprintf :会内部使用 []byte实现,不像直接运算符这种会产生很多临时的字符串,但是内部的逻辑比较复杂,有很多额外的判断,还用到了 interface,所以性能也不是很好。
func Sprintf(b *testing.B) {hello := "hello"world := "world"for i := 0; i < b.N; i++ {_ = fmt.Sprintf("%s,%s", hello, world)}
}
2.strings.Join :内部是[]byte的append。join会先根据字符串数组的内容,计算出一个拼接之后的长度,然后申请对应大小的内存,一个一个字符串填入,在已有一个数组的情况下,这种效率会很高,但是本来没有,去构造这个数据的代价也不小。
func Join(b *testing.B) {hello := "hello"world := "world"for i := 0; i < b.N; i++ {_ = strings.Join([]string{hello, world}, ",")}
}
3.bytes.Buffer :可以预先分配大小,减少对象分配与拷贝。
func Buffer(b *testing.B) {hello := "hello"world := "world"for i := 0; i < b.N; i++ {var buffer bytes.Bufferbuffer.WriteString(hello)buffer.WriteString(",")buffer.WriteString(world)_ = buffer.String()}
}
本文发布于:2024-01-28 01:10:16,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/17063754223754.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |