0%

“是时候好好理清那些重要的基础概念和系统知识了” ——很久之后,当你从泥泞的业务项目代码中脱身时,总是会这样想到。
本文主要是对阅读过的一些好的博客文章做一个汇总整理(参考博客地址见文章末尾),为了查阅方便,以及防止哪天这些博客的文章意外不见

Table of Contents

前言

本文讨论的背景是Linux环境下的network IO。
本文最重要的参考文献是Richard Stevens的“UNIX® Network Programming Volume 1, Third Edition: The Sockets Networking ”,6.2节“I/O Models ”,Stevens在这节中详细说明了各种IO的特点和区别,如果英文够好的话,推荐直接阅读。Stevens的文风是有名的深入浅出,所以不用担心看不懂。本文中的流程图也是截取自参考文献。

Stevens在文章中一共比较了五种IO Model:
blocking IO
nonblocking IO
IO multiplexing
signal driven IO
asynchronous IO
由于signal driven IO在实际中并不常用,所以我这只提及剩下的四种IO Model。

再说一下IO发生时涉及的对象和步骤。
对于一个network IO (这里我们以read举例),它会涉及到两个系统对象,一个是调用这个IO的process (or thread),另一个就是系统内核(kernel)。当一个read操作发生时,它会经历两个阶段:
1 等待数据准备 (Waiting for the data to be ready)
2 将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)
记住这两点很重要,因为这些IO Model的区别就是在两个阶段上各有不同的情况。

blocking IO

在linux中,默认情况下所有的socket都是blocking,一个典型的读操作流程大概是这样:

blocking-io-model

当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据。对于network io来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的UDP包),这个时候kernel就要等待足够的数据到来。而在用户进程这边,整个进程会被阻塞。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来。
所以,blocking IO的特点就是在IO执行的两个阶段都被block了。

non-blocking IO

linux下,可以通过设置socket使其变为non-blocking。当对一个non-blocking socket执行读操作时,流程是这个样子:

non-blocking-io

从图中可以看出,当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。
所以,用户进程其实是需要不断的主动询问kernel数据好了没有。

IO multiplexing

IO multiplexing这个词可能有点陌生,但是如果我说select,epoll,大概就都能明白了。有些地方也称这种IO方式为event driven IO。我们都知道,select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。它的基本原理就是select/epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。它的流程如图:

multiplexing-io

当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。
这个图和blocking IO的图其实并没有太大的不同,事实上,还更差一些。因为这里需要使用两个system call (select 和 recvfrom),而blocking IO只调用了一个system call (recvfrom)。但是,用select的优势在于它可以同时处理多个connection。(多说一句。所以,如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。)
在IO multiplexing Model中,实际中,对于每一个socket,一般都设置成为non-blocking,但是,如上图所示,整个用户的process其实是一直被block的。只不过process是被select这个函数block,而不是被socket IO给block。

Asynchronous I/O

linux下的asynchronous IO其实用得很少。先看一下它的流程:

Asynchronous I/O

用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。

总结

到目前为止,已经将四个IO Model都介绍完了。现在回过头来回答最初的那几个问题:blocking和non-blocking的区别在哪,synchronous IO和asynchronous IO的区别在哪。
先回答最简单的这个:blocking vs non-blocking。前面的介绍中其实已经很明确的说明了这两者的区别。调用blocking IO会一直block住对应的进程直到操作完成,而non-blocking IO在kernel还准备数据的情况下会立刻返回。

在说明synchronous IO和asynchronous IO的区别之前,需要先给出两者的定义。Stevens给出的定义(其实是POSIX的定义)是这样子的:
A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes;
An asynchronous I/O operation does not cause the requesting process to be blocked;
两者的区别就在于synchronous IO做”IO operation”的时候会将process阻塞。按照这个定义,之前所述的blocking IO,non-blocking IO,IO multiplexing都属于synchronous IO。有人可能会说,non-blocking IO并没有被block啊。这里有个非常“狡猾”的地方,定义中所指的”IO operation”是指真实的IO操作,就是例子中的recvfrom这个system call。non-blocking IO在执行recvfrom这个system call的时候,如果kernel的数据没有准备好,这时候不会block进程。但是,当kernel中数据准备好的时候,recvfrom会将数据从kernel拷贝到用户内存中,这个时候进程是被block了,在这段时间内,进程是被block的。而asynchronous IO则不一样,当进程发起IO 操作之后,就直接返回再也不理睬了,直到kernel发送一个信号,告诉进程说IO完成。在这整个过程中,进程完全没有被block。

各个IO Model的比较如图所示:

经过上面的介绍,会发现non-blocking IO和asynchronous IO的区别还是很明显的。在non-blocking IO中,虽然进程大部分时间都不会被block,但是它仍然要求进程去主动的check,并且当数据准备完成以后,也需要进程主动的再次调用recvfrom来将数据拷贝到用户内存。而asynchronous IO则完全不同。它就像是用户进程将整个IO操作交给了他人(kernel)完成,然后他人做完后发信号通知。在此期间,用户进程不需要去检查IO操作的状态,也不需要主动的去拷贝数据。

附录:

参考博客:https://blog.csdn.net/historyasamirror/article/details/5778378

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//冒泡排序
func bubbleSort(arr []int) {
length := len(arr)
for i := 0; i < length; i++ {
for j := 0; j < length-1-i; j++ {
if arr[j] > arr[j+1] {
arr[j], arr[j+1] = arr[j+1], arr[j]
}
}
}
}

//选择排序
func selectSort(arr []int) {
length := len(arr)
for i := 0; i < length; i++ {
for j := i + 1; j < length; j++ {
if arr[i] > arr[j] {
arr[i], arr[j] = arr[j], arr[i]
}
}
}
}

//插入排序
func insertSort(arr []int) {
length := len(arr)
for i := 1; i < length; i++ {
for j := i; j >= 0; j-- {
if j > 0 && arr[j] < arr[j-1]{
arr[j-1], arr[j] = arr[j], arr[j-1]
}
}
}
}

排序算法

时间复杂度

空间复杂度

稳定性

排序方式

冒泡排序

O(_n_2)

O(1)

稳定

In-place

选择排序

O(_n_2)

O(1)

不稳定

In-place

插入排序

O(_n_2)

O(1)

稳定

In-place

希尔排序

O(nlogn)

O(1)

不稳定

In-place

归并排序

O(nlogn)

O(n)

稳定

Out-place

快速排序

O(nlogn)

O(logn)

不稳定

In-place

堆排序

O(nlogn)

O(1)

不稳定

In-place

计数排序

O(n+k)

O(k)

稳定

Out-place

桶排序

O(n+k)

O(n+k)

稳定

Out-place

基数排序

O(n×k)

O(n+k)

稳定

Out-place

参考:https://zhuanlan.zhihu.com/p/124356219

时间复杂度:O(nlogn)
空间复杂度:O(n)
稳定性:稳定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

func mergeSort(arr []int) {
temp := make([]int, len(arr))
mergeRecursive(arr, temp, 0, len(arr) - 1)
}

//归并排序
func mergeRecursive(arr []int, result []int, start, end int) {
if start >= end {
return
}
end1 := (end-start)>>1 + start
start1, start2 := start, end1 + 1
mergeRecursive(arr, result, start1, end1)
mergeRecursive(arr, result, start2, end)
k := start
for ; start1 <= end1 && start2 <= end; {
if arr[start1] < arr[start2] {
result[k] = arr[start1]
start1++
} else {
result[k] = arr[start2]
start2++
}
k++
}
for ; start1 <= end1; {
result[k] = arr[start1]
start1++
k++
}
for ; start2 <= end; {
result[k] = arr[start2]
start2++
k++
}
for k = start; k <= end; k++ {
arr[k] = result[k]
}
}

参考链接:https://www.cnblogs.com/chengxiao/p/6129630.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

//堆排序
func headSort(arr []int) {
length := len(arr)
for i := length/2 - 1; i >= 0; i-- {
heap(arr, i, length)
}

fmt.Println(arr)
for j := length - 1; j > 0; j-- {
arr[0], arr[j] = arr[j], arr[0]
heap(arr, 0, j)
}
}

func heap(arr []int, start int, length int) {
temp := arr[start]
fmt.Println(start, length, arr)
for i := start*2 + 1; i < length; i = i*2 + 1 {
if i+1 < length && arr[i] < arr[i+1] {
i++
}
if arr[i] > temp {
arr[start], start = arr[i], i
} else {
break
}
}
arr[start] = temp
}

题目:https://leetcode-cn.com/problems/reverse-nodes-in-k-group/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func reverseKGroup(head *ListNode, k int) *ListNode {
if k <= 1 head == nil head.Next == nil {
return head
}
start,end,next,oldEnd, count := head,head,head,head, 0
for loop := 1;end != nil; loop++{
if loop % k == 0 {
next,end.Next = end.Next, nil
newHead := reverseList(start)
if loop == k {
head = newHead
}else{
oldEnd.Next, oldEnd = newHead, start
}
start.Next,start,end = next,next,next
}else{
end = end.Next
count++
}
}
if count % k == 0 {
reverseList(start)
}
return head
}

func reverseList(head *ListNode) *ListNode {
var prev *ListNode
for head != nil {
head.Next, prev, head = prev, head, head.Next
}
return prev
}

路漫漫其修远兮!

题目:https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
func lengthOfLongestSubstring(s string) int {
sLen := len(s)
if sLen <= 1 {
return sLen
}
tempMap := make([]int, 256) // 字符词频表
right := 0
maxLen := 0
for left := 0; left < sLen; left++ {
for ; right < sLen && tempMap[s[right]] == 0; right++ {
tempMap[s[right]]++
}
fmt.Println(tempMap)
maxLen = max(maxLen, right - left)
if right == sLen {
break
}
tempMap[s[left]]--
}
return maxLen
}

func max(x, y int) int {
if x < y {
return y
}
return x
}

为什么我自己写出来的跟这个性能差这么多,哎, 去抄10遍吧!

题目: https://leetcode-cn.com/problems/reverse-linked-list/

多指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func reverseList(head *ListNode) *ListNode {

var prev *ListNode
for head != nil {
// next := head.Next
// head.Next = prev
// prev = head
// head = next
head.Next, prev, head = prev, head, head.Next
}
return prev
}

递归YYDS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func reverseList(head *ListNode) *ListNode {


if head == nil head.Next == nil {
return head
}
curr := reverseList(head.Next)
head.Next.Next = head
head.Next = nil
return curr
}

快排

哎,比想象中的花时间啊!

代码这东西,还是保持手感。唯手熟尔。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
func sortArray(nums []int) []int {
start := 0
left := 0
right := len(nums) - 1
end := len(nums)
if left >= right len(nums) <= 1{
return nums
}

pos := (right - left) / 2
mid := nums[pos]
nums[left], nums[pos] = nums[pos], nums[left]
for left < right{
for nums[right] >= mid && left < right{
right--
}
nums[left], nums[right] = nums[right], nums[left]

for nums[left] <= mid && left < right{
left++
}
nums[left], nums[right] = nums[right], nums[left]
}

sortArray(nums[right+1:end])
sortArray(nums[start:left])

return nums
}