操作系统会为该应用程序创建一个进程。作为一个应用程序,它像一个为所有资源而运行的容器。这些资源包括内存地址空间、文件句柄、设备和线程。
线程是操作系统调度的一种执行路径,用于在处理器执行我们在函数中编写的代码。一个进程从一个线程开始,即主线程,当该线程终止时,进程终止。这是因为主线程是应用程序的原点。然后,主线程可以依次启动更多的线程,而这些线程可以启动更多的线程。
无论线程属于哪个进程,操作系统都会安排线程在可用处理器上运行。每个操作系统都有自己的算法来做出这些决定。
Go语言层面支持的go关键字,可以快速的让一个函数创建为goroutine,我们可以认为main函数就是作为goroutine执行的。操作系统调度线程在可用处理器上运行,Go运行时调度goroutines在绑定到单个操作系统线程的逻辑处理器中运行(P)。即使使用这个单一的逻辑处理器和操作系统线程,也可以调度数十万goroutine以惊人的效率和性能并发运行。
Concurrency is not Parallelism.(并发是轮流处理多个任务,并行是同时处理多个任务。)
并发不是并行。并行是指两个或多个线程同时在不同的处理器执行代码。如果将运行时配置为使用多个逻辑处理器,则调度程序将在这些逻辑处理器之间分配goroutine,这将导致goroutine在不同的操作系统线程上运行。但是,要获得真正的并行性,您需要在具有多个物理处理器的计算机上运行程序。否则,goroutines将针对单个物理处理器并发运行,即使Go运行时使用多个逻辑处理器。
如果你的goroutine在从另一个goroutine获得结果之前无法取得进展,
那么通常情况下,你自己去做这项工作比委托它( go func() )更简单。
这通常消除了将结果从goroutine返回到其启动器所需的大量状态跟踪和chan操作。
把并发的决定权交给调用方,从而防止有隐藏的不可控的GoRoutine出现,确保GoRoutine对于调用方来说,是可见的。
永远不要启动一个你不知道何时会结束的GoRoutine,
启动一个不止到何时会结束的GoRoutine会导致可能出现的GoRoutine泄漏,从而导致内存泄露,服务挂掉
如何保证在一个goroutine中看到在另一个goroutine修改的变量的值,如果程序中修改数据时有其他goroutine同时读取,那么必须将读取串行化。为了串行化访问,请使用channel或其他同步原语,例如sync和sync/atomic来保护数据。
在一个goroutine中,读和写一定是按照程序中的顺序执行的。
即编译器和处理器只有在不会改变这个goroutine的行为时才可能修改读和写的执行顺序。
由于重排,不同的goroutine可能会看到不同的执行顺序。
例如,一个goroutine执行a = 1;b = 2;,另一个goroutine可能看到b在a之前更新。
func main() {
var a, b int
go func() {
time.Sleep(time.Microsecond)
a = 1
fmt.Println(b)
}()
go func() {
time.Sleep(time.Microsecond)
b = 1
fmt.Println(a)
}()
time.Sleep(time.Second)
}
用户写下的代码,先要编译成汇编代码,也就是各种指令,包括读写内存的指令。
CPU的设计者们,为了榨干CPU的性能,无所不用其极,各种手段都用上了,
你可能听过不少,像流水线、分支预测等等。
其中,为了提高读写内存的效率,会对读写指令进行重新排列,这就是所谓的 内存重排,英文为MemoryReordering。
现代CPU为了“抚平”内核、内存、硬盘之间的速度差异,搞出了各种策略,例如三级缓存等。
下图:为了让(2)不必等待(1)的执行“效果”可见之后才能执行,我们可以把(1)的效果保存到store buffer
Store Buffer 对于单线程来说是完美的
但是对于多线程,先执行(1)和(3)将他们写入 Store Buffer,接下来(2),(4)去查找变量时发现,它们所在的 CPU 的 Store Buffer 中 并没有对应的变量,于是去内存中拿,从而读出了 0和0.
因此,对于多线程的程序,所有的CPU都会提供“锁”支持,称之为barrier,或者fence。
它要求:barrier(内存屏障)指令要求所有对内存的操作都必须要“扩散”到 memory 之后才能继续执行其他对 memory 的操作。因此,我们可以用高级点的atomiccompare-and-swap ,或者直接用更高级的锁,通常是标准库提供。
为了说明读和写的必要条件,我们定义了先行发生(Happens Before)。
如果事件 e1 发生在 e2 前,我们可以说 e2 发生在 e1 后。
如果 e1 不发生在 e2 前也不发生在 e2 后,我们就说 e1 和 e2 是并行的。
注意:在单一的独立的 goroutine 中先行发生的顺序即是程序中表达的顺序。
好好学习,天天向上