计算机基础知识系列(一)

程序员基础,性能测试需要知道的一些底层概念(一)

进程、线程、程序

对操作系统来说,线程是最小的执行单元,进程是最小的资源管理单元,线程和进程都是由系统内核管理。

进程

定义

程序被加载到内存中并准备执行,就是一个进程,它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
关于进程有两点需要注意:
一、进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量;
二、进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时,它才能成为一个活动的实体,我们称其为进程。

进程状态

进程有三个状态,就绪-运行-阻塞。就绪状态其实就是获取除CPU之外的所有资源,只要处理器分配资源就可以马上执行;运行状态就是获取了处理器分配的资源,开始运行程序;阻塞状态,当程序条件不够时,需要等待条件满足才能继续执行,例如I/O。

程序

程序其实本身没有任何运行的含义,程序是指令和数据的有序集合。

线程

定义

单个进程中执行中的每个任务就是一个线程,线程是进程中执行运算的最小单位。一个线程只能属于一个进程,但是一个进程可以拥有多个线程。多线程处理就是允许一个进程中在同一时刻执行多个任务。

进程与线程的区别

1,线程没有地址空间,线程包含在进程的地址空间中。线程上下文只包含一个堆栈、一个寄存器、一个优先权,线程文本包含在他的进程的文本片段中。同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,寄存器的内容(栈段又叫运行时段,用来存放所有局部变量和临时变量)。
2,线程在执行过程中与进程还是有区别的。每个独立的进程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
3,从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。
4,线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而进程正相反。
(进程就像火车,线程就是火车的车厢)

多进程与多线程

多线程

多线程,顾名思义,同时运行多个线程;多任务可以由多进程完成,也可以由一个进程内的多线程完成。

线程状态

首先,线程大致有五种状态:初始化,就绪,运行,阻塞,死亡;

线程锁

在多线程中,每个线程都有自己的资源,但是代码区是共享的,即每个线程都可以执行相同的函数。这可能带来的问题就是几个线程同时执行一个函数,导致数据的混乱,产生不可预料的结果,因此我们必须避免这种情况的发生。例如:有两个线程,同时操作一个列表, 列表中数值都是0,一个线程是更新(update),操作为从前往后顺序更新值为1;线程二为打印(print),操作为从后往前倒序打印列表,当两个线程同时操作列表时就会出现打印时一半0,一半1,出现了数据不同步,为了避免这样的情况,就引进了线程锁的概念。

锁有两种状态——锁定和未锁定。每当一个线程比如”update”要访问共享数据时,必须先获得锁定;如果已经有别的线程比如”print”获得锁定了,那么就让线程”update”暂停,也就是同步阻塞;等到线程”print”访问完毕,释放锁以后,再让线程”update”继续。经过这样的处理,打印列表时要么全部输出0,要么全部输出1,不会再出现一半0一半1的混乱数据。

线程通信(条件变量)

列表并不是一开始就有的;而是通过线程”create”创建的。如果”update”或者”print” 在”create”还没有运行的时候就访问列表,将会出现一个异常。使用锁可以解决这个问题,但是”update”和”print”将需要一个无限循环——他们不知道”create”什么时候会运行,让”create”在运行后通知”update”和”print”显然是一个更好的解决方案。于是,引入了条件变量。

条件变量允许线程比如”update”和”print”在条件不满足的时候(列表为None时)等待,等到条件满足的时候(列表已经创建)发出一个通知,告诉”update” 和”print”条件已经有了,你们该起床干活了;然后”update”和”print”才继续运行。

线程运行和阻塞状态转换

阻塞有三种状态:
同步阻塞(锁定池)是指处于竞争锁定的状态,线程请求锁定时将进入这个状态,一旦成功获得锁定又恢复到运行状态;
等待阻塞(等待池)是指等待其他线程通知的状态,线程获得条件锁定后,调用“等待”将进入这个状态,一旦其他线程发出通知,线程将进入同步阻塞状态,再次竞争条件锁定;
而其他阻塞是指调用time.sleep()、anotherthread.join()或等待IO时的阻塞,这个状态下线程不会释放已获得的锁定。

多进程

为啥会想看多进程这么个东西,那是因为,python说实话,有点坑的地方是,实质上python没有多线程。python虽然可以通过threading模块来实现多线程编程,但是Python的线程虽然是真正的线程,但解释器执行代码时,有一个GIL锁:Global Interpreter Lock,任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。而通常我们用的解释器是官方实现的CPython,要真正利用多核,除非重写一个不带GIL的解释器。所以使用python实现并发多线程,不太现实。。

不过即使如此,我们也可以使用多进程来实现多核任务,毕竟当前多核处理器满大街都是。

实现多进程

首先,unix中有个fork()函数,这玩意就可以实现多进程,这函数产生的效果就是把当前进程复制一份,普通的函数调用,调用一次,返回一次,但是fork()调用一次,返回两次,因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回。子进程永远返回0,而父进程返回子进程的ID。这样做的理由是,一个父进程可以fork出很多子进程,所以,父进程要记下每个子进程的ID,而子进程只需要调用getppid()就可以拿到父进程的ID。

python的os模块封装了fork(),可以很简单就实现创建子进程;但是由于windows系统没有fork(),在windows上就不能执行包含fork()调用的脚本,在windows上就用multiprocessing替代。

multiprocessing 示例

使用multiprocessing模块进行多进程编程,可以支持跨平台。

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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@Auther Kyle
@Time 2018/6/19 13:10
"""
from multiprocessing import Pool
import os, time, random


def LongTimeTask(name):
print("Run task{0} {1}".format(name, os.getpid()))
start = time.time()
time.sleep(random.random() * 3)
end = time.time()
print("Task {0} runs {1} seconds.".format(name, (end - start)))


if __name__ == '__main__':
print("Parent process {0}".format(os.getpid()))
p = Pool(4)
for i in range(5):
p.apply_async(LongTimeTask, args=(i, ))
print("Waiting for all subprocesses done...")
p.close()
p.join()
print("All subprocess done")

执行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
Parent process 1692
Waiting for all subprocesses done...
Run task0 4464
Run task1 5516
Run task2 2388
Run task3 8540
Task 3 runs 0.22301292419433594 seconds.
Run task4 8540
Task 1 runs 0.463026762008667 seconds.
Task 2 runs 1.3660781383514404 seconds.
Task 4 runs 1.2180695533752441 seconds.
Task 0 runs 1.7631008625030518 seconds.
All subprocess done
进程间通信

multiprocessing提供多种方法来交换数据:QueuePipes等。
代码示例:

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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@Auther Kyle
@Time 2018/6/19 14:56
"""
from multiprocessing import Process, Queue
import os, time, random


def write(q):
"""写数据进程执行"""
print("Process to write: {0}".format(os.getpid()))
for value in ['A', 'B', 'C']:
print("Put {0} to queue.".format(value))
q.put(value)
time.sleep(random.random())


def read(q):
"""读数据进程执行"""
print("Process to read: {0}".format(os.getpid()))
while True:
value = q.get(True)
print("Get {0} from queue.".format(value))


if __name__ == '__main__':
# 父进程创建Queue,传给各个子进程
q = Queue()
pw = Process(target=write, args=(q,))
pr = Process(target=read, args=(q,))
# 启动子进程pw,写入
pw.start()
# 启动子进程pr,读取
pr.start()
# 等待写进程执行结束
pw.join()
# pr进程里是死循环,无法等待其结束,只能强行终止
pr.terminate()

运行结果:

1
2
3
4
5
6
7
8
Process to read: 7616
Process to write: 4948
Put A to queue.
Get A from queue.
Put B to queue.
Get B from queue.
Put C to queue.
Get C from queue.

以上代码均来自于网络~

协程

协程,又称微线程,英文Coroutines,是一种比线程更加轻量级的存在。正如一个进程可以拥有多个线程一样,一个线程也可以拥有多个协程。(关于协程,真的不太懂,之后有空再研究,看到一句话:子程序就是协程的一种特例)多线程编程中,为了防止数据不同步,需要添加锁机制,但是协程应为就用了一个线程,在协程之间共享资源是不用加锁机制的,只需要判断状态。

参考链接

廖雪峰关于多进程教程
Python线程指南
廖雪峰-协程教程

文章目录
  1. 进程、线程、程序
    1. 进程
      1. 定义
      2. 进程状态
    2. 程序
    3. 线程
      1. 定义
    4. 进程与线程的区别
  2. 多进程与多线程
    1. 多线程
      1. 线程状态
      2. 线程锁
      3. 线程通信(条件变量)
      4. 线程运行和阻塞状态转换
    2. 多进程
      1. 实现多进程
      2. multiprocessing 示例
      3. 进程间通信
  3. 协程
  4. 参考链接
|