這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
上一篇部落客要是講如何避免在高並發下使用太多系統線程或進程,但如果僅僅是減少了線程數,CPU利用率本身沒有上來,那麼系統的容量很低,那麼仍然無法達到高並發的目的。
通常情況下,我們會設定線程數等於CPU數,充分利用CPU就等價於如何讓線程一直工作,避免把時間浪費在等待系統調用返回上,從而提高系統容量。
很多 linux 平台下的非同步架構都基於 epoll 來設計,但 epoll 本身只支援 fd, 也就是說能很好的支援檔案IO以及socket, 但對於其他系統調用則無法處理,在調用時仍然會造成堵塞,比如 creat, unlink等等。在大部分情況下,能對檔案IO和socket進行非同步處理已經能滿足我們的需求,比如 libevent, Java 的 NIO 都只提供了這些支援。
node.js 支援得更多一些,具體可以參考他的 fs 模組 ,對大量的系統調用同時提供了同步調用和非同步呼叫的介面。
go 語言在編程的時候無需開發人員對系統調用做特殊處理,我相信他在內部做了一些處理來支援他的高並發特性,但畢竟要眼見為實。
go 語言與系統調用的模組放在 syscall package,並在上面封裝出了 "os", "time", "net" 等模組,並且建議直接使用後面的模組,單從 syscall 裡邊函數的介面來看,看不到任何非同步非阻塞的跡象,只能從原始碼的角度來分析。
syscall package 的源碼位於 src/pkg/syscall/syscall_linux.go 等檔案,不過裡邊的函數都是簡單地對更底層函數的一個封裝,比如
//sys open(path string, mode int, perm uint32) (fd int, errno int)
func Open(path string, mode int, perm uint32) (fd int, errno int) {
return open(path, mode|O_LARGEFILE, perm)
}
真正按非同步方式的代碼位於 src/pkg/syscall/zsyscall_linux_amd64.go 這樣的檔案裡,這個檔案是由上面的檔案編譯而成的,範例如下
func open(path string, mode int, perm uint32) (fd int, errno int) {
r0, _, e1 := Syscall(SYS_OPEN, uintptr(unsafe.Pointer(StringBytePtr(path))), uintptr(mode), uintptr(perm))
fd = int(r0)
errno = int(e1)
return
}
Syscall 的定義位於 src/pkg/syscall/asm_linux_amd64.s, 是用彙編寫成的,不過我不懂彙編(淚)
TEXT ·Syscall(SB),7,$0
CALL runtime·entersyscall(SB)
MOVQ 16(SP), DI
MOVQ 24(SP), SI
MOVQ 32(SP), DX
MOVQ $0, R10
MOVQ $0, R8
MOVQ $0, R9
MOVQ 8(SP), AX // syscall entry
SYSCALL
CMPQ AX, $0xfffffffffffff001
JLS ok
MOVQ $-1, 40(SP) // r1
MOVQ $0, 48(SP) // r2
NEGQ AX
MOVQ AX, 56(SP) // errno
CALL runtime·exitsyscall(SB)
RET
ok:
MOVQ AX, 40(SP) // r1
MOVQ DX, 48(SP) // r2
MOVQ $0, 56(SP) // errno
CALL runtime·exitsyscall(SB)
RET
其中 runtime·entersyscall 和 runtime·exitsyscall 位於 src/pkg/runtime/proc.c
分析不下去了,不過能確認的是, go 語言確實做了什麼,避免了系統調用堵塞線程,保證了程式能充分使用 CPU。