函数
-
什么是
Function Signatures
? 什么是Function Types
? 什么是Function Type Literals
? 什么是Function Prototypes
? 什么是Function Declaration
?以func Double(n int) (result int)
为例,进行解释。- 函数签名 = 输入参数类型的列表 + 输出结果类型的列表。例如,
(n int) (result int)
。 - 函数类型 =
func
+ 函数签名。例如,func (n int) (result int)
。根据Type Literal
的规定,func (n int) (result int)
也被称为Function Type Literals
,属于Undefined Types
。函数类型声明的方式type Myfunc func (n int) (result int)
。 - 函数原型 = 函数名称 + 函数类型。例如,
func Double(n int) (result int)
就是一个函数原型。函数类型要采用Literal
的形式。 - 函数声明(Function Declaration,不是Function Type Declaration) = 函数原型 + 函数体。
- 以上是函数作为一等公民的必要解释。
- 函数签名 = 输入参数类型的列表 + 输出结果类型的列表。例如,
-
Function Value
具有哪些特殊性?-
Function Value
除了可以被传递,还可以被调用。由于Built-in Function
采用了“泛型”作为参数,它不是Function Value
;由于init
函数的特殊性,它也不能作为Function Value
。 - 当我们声明了一个自定义的函数,我们也声明可一个
Function Value
,它的标识就是函数名称,它不可被修改。 - 如果函数值为nil,调用时发生panic;如果是
go + 函数值调用
,会发生不可恢复的panic。 - ** All functions in Go can be viewed as closures. **
-
Goroutine
Goroutine
是Go语言提供的唯一的并发编程(concurrent Programming)手段。Go不提供创建线程的手段。当程序启动时,它只有一个协程,称为主协程(main goroutine)。你可以很方便地通过go + 函数调用,创建一个新的协程。新协程退出的时机是函数调用返回。注意,如果主协程退出,那么所有其它的协程都会退出,整个进程退出。
Goroutine
有四种状态:create
、running
、blocking
、exit
。blocking状态是指协程将会处于blocking状态,直到由其它协程唤醒。对于调用time.Sleep
函数,导致协程暂停执行的情况,我们认为协程仍然处于running状态(因为无需其它协程来唤醒)。在状态转换方向中,create只能转换为running;running能转换为blocking或exit状态;blocking只能转换为running状态。
协程的并行度的上限是逻辑CPU的个数(Logical CPU),调用runtime.NumCPU
,获取Go程序可使用的逻辑CPU的个数。
为什么不是操作系统的逻辑CPU的个数?某些逻辑CPU可能被其它进程锁定。
那么,是否意味着Go程序会启动runtime.NumCPU
个线程,跑满所有的CPU?如何控制CPU的使用量?Go支持创建成千上万个协程,多个协程对应一个线程,如果协程调用系统调用,阻塞线程,runtime.NumCPU
个线程岂不是会都处于阻塞状态?带着这些问题,我们来学习Go的调度模型。
为了提升并发的性能,要做到:降低上下文切换的代价;充分利用操作系统的多个CPU。线程上下文的切换代价越来越高,如果一个协程对应一个线程,一旦创建成千上万个线程,上下文切换将成为系统的沉重负担。一个协程对应一个线程,也会造成垃圾收集器的难于实现(只有当memory must be in a consistent state时,Go才能进行垃圾收集工作,为此,我们必须wait for the threads that are being actively run on a CPU core)。于是,Go实现了用户态的调度器。
Go采用了MPG模型。M代表系统线程,P代表上下文(Processor),G代表协程(Goroutine)。每个Processor都维护了一个runqueue
队列,队列中的每个元素都是一个都处于running状态的协程。新创建的协程会被某个Processor接收,并被放置到队尾。系统线程若想执行协程,它必须Hold住一个Processor,从队列中摘取一个协程,运行之。
如果某个协程因为执行系统调用,会不会导致对应的线程被阻塞?如果多个协程执行系统调用,会不会导致所有线程都处于阻塞状态?原先的线程会被阻塞,但Go调度器会创建一个新的线程,并将Processor转交给新的线程,继续执行队列中的其它协程。所以,P个Processors总是处于"active"状态。被阻塞的线程恢复执行后,它要么从其它线程那里偷取一个Processor,继续执行;要么将Go协程放入全局runqueue
,自己返回到线程池当中。
如何控制并发度?调用runtime.GOMAXPROCS
函数或者设置GOMAXPROCS
环境变量。For the standard Go runtime, before Go 1.5, the default initial value of this number is 1, but since Go 1.5, the default initial value of this number is equal to the number of logical CPUs available for the current running program.
M与P的数量关系?默认情况下,M等于P。你通过runtime.GOMAXPROCS
可以设置P为更大的值;P在超过逻辑CPU个数之后,更大的P值没什么意义。此时,一个M可能会对应多个P。
如何保证负载均衡?如果某些Processor的队列已空,对应的线程将没有工作可做。调度器会从其它Processor的队列中,偷取Go协程,确保所有线程的负载均衡。
Concurrency Synchronization
当一块内存同时被读写时(多一个协程读、一个协程写;多个协程同时写),会破坏数据的完整性。这种情况被称为data race
。
协程也是一种资源,处于blocking状态的协程,如果再也没有机会被唤醒,那就存在资源泄露。所以,当你创建协程时,需要考虑以下情况:1. 需要创建多少个协程;2. 这些协程何时启动、阻塞、解除阻塞、退出;3. 如何为这些协程分配任务。
concurrency synchronization
或data synchronization
就是负责解决这些问题。Go提供了三种进程内同步手段:1. channel
;2. sync package
;3. sync/atomic
。channel和goroutine大大降低了并发编程的难度,Gopher很乐意使用它们,而忽略了传统的data sync
技术(包括sync和sync/atomic)。
Channels
-
Channel的可比较的(comparable)?
- 综合Channel的内部表示
type _channel *channelImpl
,两个channel
相等的条件是:_channel
的值相等。 - 不考虑channel内部的element。
- 综合Channel的内部表示
-
Channel的五大操作?
- close(ch)
- <- ch
- ch <- v
- len(ch)
- cap(ch)
- 以上五大操作都是同步操作(synchronized operations)。但Go语言中的绝大部分操作都不是同步操作,甚至包括:
v = <- ch
的赋值给v的部分;ch1 := ch2
。
Operation | A Nil Channel | A Closed Channel | A Not-Closed Non-Nil Channel |
---|---|---|---|
Close | panic | panic | succeed to close |
Send Value | To block for ever | panic | block or succeed to send |
Receive Value | From block for ever | never block | block or succeed to receive |
-
Channel内部的三个队列
-
the receiving goroutine queue
这是一个链表结构的队列,没有长度限制。在这个队列中的协程都处于阻塞状态(blocking state),等待从该Channel中获取数据; -
the sending goroutine queue
这也是一个链表结构的队列,没有长度限制。在这个队列中的协程都处于阻塞状态(blocking state),等待向该Channel发送数据。 等待被发送的数据也存储在这个队列中。 -
the value buffer queue
这是一个环形队列,它的大小等于Channel的容量(capacity). 队列中存储的是Channel的元素。 该队列有两个特殊状态:full status(环形队列装满了值) 和 empty status(环形队列为空). 对于unbuffered channel,队列既处于full status也处于empty status。 - 另外,这个Channel还拥有一个互斥锁(mutex lock)
-
-
当协程从一个"not-nil not-closed channel"中获取数据
- 如果
value buffer queue
不为空(此时receiving goroutine queue
一定为空),协程A将会从value buffer queue
中摘下一个元素(A接收到的元素)。 如果sending goroutine queue
此时也不为空,一个发送协程将会被摘下(同时,协程进入running
状态);发送的数据将会被放入value buffer queue
。 协程A一直处于running状态(non-blocking operation
)。 - 如果
value buffer queue
为空。如果sending goroutine queue
不为空,说明存在阻塞的协程(说明Channel本身是一个unbuffered channel
) 一个发送协程将会被摘下, 进入running
状态, 发送的数据直接返回给协程A。 协程A一直处于running
状态(non-blocking operation)。 - 如果
value buffer queue
为空且sending goroutine queue
为空, 协程A将会被挂载到receiving goroutine queue
,并进入阻塞(blocking)状态.
- 如果
当协程向一个"not-nil not-closed channel"中发送数据
如果
receiving goroutine queue
不为空,此时value buffer queue
一定为空(如果value buffer queue不为空,receiving goroutine queue就应该为空才对)。 一个接收协程将会被摘下(进入running
状态),并将数据发送给该协程。如果
receiving goroutine queue
为空,此时如果value buffer queue
还有存储空间(没有达到full
状态),此时sending goroutine queue
一定为空。协程A将数据放入value buffer queue
。如果
receiving goroutine queue
为空,此时如果value buffer queue
已经处于full状态,协程A将会被挂载到sending goroutine queue
, 进入阻塞状态。-
当协程关闭一个"not-nil not-closed channel"
- 如果
receiving goroutine queue
不为空(此时value buffer queue
一定为空), 所有被挂载的协程将会被摘下,并设置为running
状态,每个协程返回了零值。 - 如果
sending goroutine queue
不为空, 每一个被挂载的发送协程将会被摘下,每个协程将会产生一个panic
(for sending on a closed channel)。 注意:value buffered queue中的数据仍然存在,仍然可以被正常消费。
- 如果
-
当协程从一个"not-nil closed channel"中获取数据
- 如果
value buffered queue
中存在数据,则从队列中取出第一个元素,并返回。 - 如果
value buffered queue
中没有数据,则返回零值。 - 接收操作的第二个参数表示是否还有数据要继续读,If the second return result is false, then the first return result (the received value) must be a zero value of the element type of the channel.
- 如果
-
两个协程之间通过channel(communication)进行数据拷贝
- 如果channel是
unbuffered
类型,则数据拷贝一次; - 如果channel是
buffered
类型,数据拷贝两次。
- 如果channel是
-
for-range On Channels
- 直到管道关闭且value buffer queue为空(until the channel is closed and its value buffer queue becomes blank.)
- 注意,如果aChannel等于nil,则进入死循环。相当于receive from a nil channel。
-
select
的工作原理- 求值。evaluate all involved channel expressions and value expressions to be potentially sent in case operations, from top to bottom and left to right. Destination values for receive operations (as source values) in assignments needn’t to be evaluated at this time.
- 随机化case分支。The default branch is always put at the last position in the result order. Channels may be duplicate in the case operations.
- 对所有Channel进行排序(与第2步的结果没关系)。sort all involved channels in the case operations to avoid deadlock in the next step. No duplicate channels stay in the first N channels of the sorted result, where N is the number of involved channels in the case operations. Below, the channel lock order is a concept for the first N channels in the sorted result.
- 加锁。lock (a.k.a., acquire the locks of) all involved channels by the channel lock order produced in last step.
-
- Poll。(poll each branch in the select block by the randomized order produced in step 2)
- if this is a case branch and the corresponding channel operation is a send-value-to-closed-channel operation, 解锁并panic. Go to step 12.
- if this is a case branch and the corresponding channel operation is non-blocking, 执行channel读写操作,解锁,执行分支代码. Go to step 12.
- if this is the default branch, 解锁并执行default之分. Go to step 12.
- 从这一步开始将处理block operation的步骤。将协程放入receiving goroutine queue 或 sending goroutine queue. 因为多个case分支可以对同一个channel操作,故channel的两个队列可以同时拥有对这个协程的引用。每一个队列可能会有多个对这个协程的引用。
- 当前协程进入休眠状态,并释放所有的锁
- waiting other channel operations to wake up the current goroutine
- 如果其它协程(B)是a channel send/receive 操作,那么B会从goroutine queue中摘下一个协程(select的那个协程),select协程还记录了自己所在的case分支。
- 加锁
-
- 对select所涉及到的channels进行检查,并从它们的goroutine queue中再一次摘除下来(毕竟,select协程已经处于running状态了) dequeue the current goroutine from the receiving goroutine queue or sending goroutine queue of the involved channel in each case operation.
- if the current goroutine is waken up by a channel close operation, go to step 5. 其它协程的close操作并不能对应到具体的case语句,重新进行Poll.
- if the current goroutine is waken up by a channel send/receive operation, unlock并执行case语句
- Done.
method
-
Method Declaration的要求?
- T 必须是
Defined Types
; - T 不能是
Pointer Type
或Interface Type
- T 和 它的方法必须是在同一个package中。
- T 必须是
-
编译器的额外作用?
- 编译器为每个方法生成对应的函数。
- 如果你创建了方法
func (b Book) Pages() int
,那么你可以调用Book.Pages
函数。 - 如果你创建了
func (b *Book) SetPages(pages int)
方法,你可以调用(*Book).SetPages(&book, 123)
。 - 编译器还会重写方法,让方法调用生成的函数。
- 如果你创建了方法
- 对于
value receiver type
的方法A。- 编译器为
value receiver type
的方法生成对应的函数X。 - 编译器为
pointer receiver type
生成对应的方法B。 - 编译器为
pointer receiver type
的方法,生成对应的函数Y。 - A,B,Y的内部只有一行代码,调用函数X。
- 编译器为
- 编译器为每个方法生成对应的函数。
-
Method Prototypes
和Method Sets
- 方法原型与函数原型类似,只是缺少了关键字func,例如,
SetPages(pages int)
。 - 一个类型的所有方法构成了
Method Sets
。所有类型都有一个Method Set
,即使是空。 - 一个Interface类型可以等价于一个
Method Set
。即,Interface的类型信息并没有那么地重要。比如,如果两个Defined Interface
的Method Set
相同,那么它们就可以相互赋值。
- 方法原型与函数原型类似,只是缺少了关键字func,例如,
-
Method Values
和Method Calls
-
s.m
称为Method Value
,s.m()
称为Method Call
-
s.m
的类型信息是Function Type
,内容不包括receiver
部分。也可以作为Function Value
。
-
-
方法的receiver类型应该是
value
还是pointer
?- If the size of a value receiver type is large, then the receiver argument copy cost may be not negligible. Pointer types are all small-size types.
- Values of the types in the sync standard package should not be copied, so defining methods with value receivers for struct types which embedding the types in the sync standard package is problematic.
- Declaring methods of both value receivers and pointer receivers for the same base type is more likely to cause data races if the declared methods are called concurrently in multiple goroutines.
- Too many pointer copies may cause heavier workload for garbage collector.
- If it is hard to make a decision whether a method should use a pointer receiver or a value receiver, then just choose the pointer receiver way.
Interface
-
Interface的内部结构
- Blank Interface Type
type _interface struct { dynamicType *_type // the dynamic type dynamicValue unsafe.Pointer // the dynamic value }
- non-Blank Interface Type
type _interface struct { dynamicTypeInfo *struct { dynamicType *_type // the dynamic type methods []*_function // method table } dynamicValue unsafe.Pointer // the dynamic value }
- Blank Interface Type
-
什么是
Interface Types
?-
Interface Type
基本上等价于它的Method Set
。如果两个不同的Defined Interface Type
,它们的Method Set一致,那么它们就可以相互赋值。这引入了另外一个概念Implementation
。 - 所有的类型都拥有一个
Method Set
,例如,int类型的Method Set
为空,它实现了interface{}
接口。
-
-
Implementations
的定义?- If the method set of an arbitrary type T, T may be an interface type or not, is a super set of the method set of an interface type I, then we say type T implements interface I.
- In Go, if a type T implements an interface type I, then any value of type T can be implicitly converted to type I.
-
什么是
Value Boxing
? 赋值的过程都发生了什么?- 如果类型T实现了接口I,
i = t
操作才合法,才会发生Boxing
。 - if type T is a non-interface type, then a copy of the T value is boxed (or encapsulated) into the result (or destination) I value. 这是O(N)操作。
- if type T is also an interface type, then a copy of the value boxed in the T value is boxed (or encapsulated) into the result (or destination) I value. 通过编译器优化,这是O(1)操作。
- 如果类型T实现了接口I,
-
i = nil
VSi = []int(nil)
的区别?-
i = nil
将变量i设置为Zero Value,清除它的Dynamic Value
和Dynamic Type
。 -
i = []int(nil)
设置i的值,它的Dynamic Type
是[]int
类型,它的Dynamic Value
是nil。所以i不是Zero Value。
-
-
Type Assertion的四种情况
- convert a non-interface value to an interface value, where the type of the non-interface value must implement the type of the interface value.
- convert an interface value to an interface value, where the type of the source interface value must implement the type of the destination interface value.
- convert an interface value to a non-interface value, where the type of the non-interface value must implement the type of the interface value.
- convert an interface value to an interface value, where the type of the source interface value may or may not implement the type of the destination interface value.
针对后两种情况,需要执行t := i.(T) 或 t, ok := i.(T)
操作,称为Type Assertion
。
-
Interface values involved comparisons
- 如果一个是interface类型,一个是非interface类型,则非interface类型需要实现了interface类型,将非interface类型转换为interface类型。再比较。
- 两个不同类型的interface之间是可以比较的!!
- if one of the two interface values is a nil interface value, then the comparison result is whether or not the other interface value is also a nil interface value.
- if the dynamic types of the two interface values are two different types, then the comparison result is false
- for the case of the dynamic types of the two interface values are the same type
- if the same dynamic type is an incomparable type, a panic will occur.
- otherwise, the comparison result is the result of comparing the dynamic values of the two interface values.
Type Embedding
-
对
Embedded Type
的要求是什么?- 嵌套的两种形式
T or *T
。 - T必须是拥有自己的
Identifier
- T 不能是
Defined Pointer Type
或者a pointer type whose base type is a pointer or interface type
- 嵌套的两种形式
-
Type Embedding
的本质是什么?- 每个
Anonymous Field
都有名字,与Type Name
同名。这要求Embedded Type
不一定是Defined Type
,但一定能denoted by a name
,特指Type Alias Definition
。 -
*int
和int
不能同时作为Embedded Type
,否则出现了同名的冲突。 -
c.x
其实是a Shorthands of Selectors
。它可以表示为:c.A.x
,c.B.x
,c.B.A.x
。其中,最后一种形式成为full form
。所以,thedepth
of the selector c.x is 2。根据selector depth
我们定义了Selector Shadowing
和Selector Colliding
的规则。
- 每个
-
假设S是
Embedding Type
,T是Embedded Type
,解释编译器为S生成的隐式方法(Implicit Methods)- 针对T的方法
m
,如果它既没有与其它方法发生冲突(Selector Colliding),也没有发生Selector Shadowing
,编译器为S生成相同的方法原型。同时,编译器也会为*S
生成相同的方法原型。 - 针对
*T
的方法m
,如果它既没有与其它方法发生冲突(Selector Colliding),也没有发生Selector Shadowing
,编译器只会为*S
生成相同的方法声明。 - 所以,type struct{T} and type *struct{T} both obtain all the methods of the type denoted by T.
- 所以,type struct{T}, type struct{T}, and type struct{T} obtains all the methods of type *T.
- 所以,S即使是
Undefined Type
,你虽不能定义自己的方法,但是通过Type Embedding
技术,编译器会为你生成一些方法。
- 针对T的方法
-
Type Embedding
例子type MyInt int func (mi MyInt) IsOdd() bool { return mi%2 == 1 } type Age MyInt // Age不包含任何方法,因为不是`Embed` type X struct { // X 有Double和IsOdd两个方法 MyInt } func (x X) Double() MyInt { return x.MyInt + x.MyInt } type Y struct { // Y 不含有任何方法 Age } type Z X // Z 只包含IsOdd方法