go并发关键词 go,chan

进程

狭义定义:进程是正在运行的程序的实例(an instance of a computer program that is being executed)。

广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。

进程的概念主要有两点:第一,进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。第二,进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时(操作系统执行之),它才能成为一个活动的实体,我们称其为进程。 [3]

进程是操作系统中最基本、重要的概念。是多道程序系统出现后,为了刻画系统内部出现的动态情况,描述系统内部各道程序的活动规律引进的一个概念,所有多道程序设计操作系统都建立在进程的基础上。

线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。

线程是独立调度和分派的基本单位。线程可以为操作系统内核调度的内核线程,如Win32线程;由用户进程自行调度的用户线程,如Linux平台的POSIX Thread;或者由内核与用户进程,如Windows 7的线程,进行混合调度。

同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。

一个进程可以有很多线程,每条线程并行执行不同的任务。

在多核或多CPU,或支持Hyper-threading的CPU上使用多线程程序设计的好处是显而易见,即提高了程序的执行吞吐率。在单CPU单核的计算机上,使用多线程技术,也可以把进程中负责I/O处理、人机交互而常被阻塞的部分与密集计算的部分分开来执行,编写专门的workhorse线程执行密集计算,从而提高了程序的执行效率。


线程模型

goroutine由go runtime 进行调度,一个线程下面可能会有多个goroutine,go运行时负责把goroutine绑定到单个操作系统线程的逻辑处理器上(P),即使是单一cpu也可以轻易支持十万goroutine 高效运行

并发不是并行

并发不是并行。并行是指两个或多个线程同时在不同的处理器执行代码。如果将运行时配置为使用多个逻辑处理器,则调度程序将在这些逻辑处理器之间分配 goroutine,这将导致 goroutine 在不同的操作系统线程上运行。但是,要获得真正的并行性,您需要在具有多个物理处理器的计算机上运行程序。否则,goroutine 将针对单个物理处理器并发运行,即使 Go 运行时使用多个逻辑处理器。

永远不要在不知道何时停止的情况下启动 goroutine

最重要的原则,而且在开发中最容易遇到的问题。我们前期也写过很多这样的代码,而且我看大家使用的项目基本也都是在需要启动一个goroutine去执行代码的时候是这样写的

很少有人去关心启动的这三个goroutine应该在什么情况下去关闭,应该怎么关闭,他们得运行状态是怎么样的,会泄露资源吗?

使用goroutine前 要关心 goroutine什么时候结束且注意 goroutine什么情况下会阻塞


不要过度使用goroutine

启动一个gorotine应该是执行程序的,自己执行或者被人调用执行,不应该启动gorotine之后这个gorotine啥事都没干。如果一个goroutine的返回结果非常重要,没有这个结果无法进行下一步,那么通常情况下,你自己去做这项工作比委托它( go func() )更简单。这通常消除了将结果从 goroutine 返回到其启动器所需的大量状态跟踪和 chan 操作。

Bad Code

Good Code

Go 程序员过度使用 goroutine,尤其是在他们刚开始的时候。与生活中的很多事物一样,适度是成功的关键。

将并发留给调用者(Leave concurrency to the caller)

下面这2个函数有什么区别

明显的区别在于,第一个示例将目录读入一个切片,然后返回整个切片,或者在出现错误时返回错误。这是同步发生的,ListDirectory的调用者阻塞,直到读取了所有目录条目。根据目录的大小,这可能需要很长时间,并且可能会分配大量内存,从而形成目录条目名称的幻灯片。

让我们来看第二个例子。这有点像,ListDirectory返回一个通道,通过该通道传递目录条目。当通道关闭时,这表示没有更多的目录条目。由于通道填充发生在ListDirectory返回后,ListDirectory可能正在启动goroutine来填充通道。

第二个版本没有必要实际使用Go例程;它可以分配一个足以容纳所有目录项而不阻塞的通道,填充通道,关闭通道,然后将通道返回给调用者。但这不太可能,因为这与在一个通道中缓冲所有结果时消耗大量内存有相同的问题。

ListDirectory的通道版本还有两个问题:

通过使用闭合通道作为没有更多要处理的项目的信号,ListDirectory无法告诉调用方通过通道返回的项目集不完整,因为中途遇到了错误。调用者无法区分空目录和完全从目录读取的错误。两者都会导致从ListDirectory返回的通道立即关闭。

调用者必须继续从通道中读取数据,直到通道关闭,因为这是调用者知道开始填充通道的goroutine已停止的唯一方式。这是对ListDirectory使用的严重限制,调用方必须花费时间从通道中读取,即使它可能已经收到了它想要的答案。对于大中型目录,它可能在内存使用方面更有效,但这种方法并不比原始的基于切片的方法快。

解决这两种实现问题的方法是使用回调,即在执行每个目录项时在其上下文中调用的函数。

func ListDirectory(dir string,fn func(string))

毫不奇怪,文件路径是这样的。WalkDir功能有效。