基础概念
进程
是一个执行中的程序,即将程序装载到内存中,系统为它分配资源的这一过程。进程是操作系统资源分配的基本单位。
每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。
-
- 文本区域存储处理器执行的代码;
- 数据区域存储变量和进程执行期间使用的动态分配的内存;
- 堆栈区域存储着活动过程调用的指令和本地变量。
进程状态
进程与程序
- 程序是指令数据的集合,是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念;
- 程序和进程无一一对应关系,一个程序可由多个进程共用,一个进程在活动中又可顺序地执行若干个程序;
- 进程是一个能独立运行的单位,能与其他进程并发执行,进程是作为资源申请和调度单位存在的;而通常的程序段不能作为一个独立运行的单位。
线程
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
python不管PC有几核,在同一核同一时刻执行的线程只有一个,python是调用系统的原生线程。
线程与进程
- 进程要操作CPU,必须要先创建一个线程,所有在同一个进程里的线程是共享同一块内存空间的;线程共享内存空间,进程的内存是独立的;
- 同一个进程的线程之间可以直接交流,两个进程想通信,必须通过一个中间代理来实现;
- 创建新线程很简单, 创建新进程需要对其父进程进行一次克隆;
- 一个线程可以控制和操作同一进程里的其他线程,但是进程只能操作子进程。
同步与异步
同步:是指一个进程在执行某个请求的时候,若这个请求没有执行完成,那么这个进程将会一直等待下去,直到这个请求执行完毕,才会继续执行下面的请求。
异步:是指一个进程在执行某个请求的时候,如果这个请求没有执行完毕,进程不会等待,而是继续执行下面的请求。
并发与并行
并发:计算机的操作系统通过时间片轮转法等算法调度交替执行不同的任务。
并行:同时执行不同的任务。
多线程
线程调用方式
直接调用
import threading import time def run(*args): # 线程要运行的函数 print('test',args) time.sleep(3) t1 = threading.Thread(target=run, args=('t1',)) t2 = threading.Thread(target=run, args=('t2',)) t1.start() t2.start() print(t1.getName()) # Thread-1 print(t2.getName()) # Thread-2
继承式调用
1 class MyThread(threading.Thread): 2 3 def __init__(self, n ): 4 super(MyThread, self).__init__() 5 self.n = n 6 7 def run(self): # 必须写run函数 8 print(self.n) 9 time.sleep(2) 10 11 t1 = MyThread('t1') 12 t2 = MyThread('t2') 13 t1.start() 14 t2.start() 15 16 print(t1.getName()) 17 print(t2.getName())
线程方法
t.join(n) 表示主线程等待子线程多少时间,n表示主线程等待子线程的超时时间,如果在n时间内子线程未完成,主线程不在等待,执行后面的代码 t.run() 线程被cpu调度后自动执行线程对象的run方法(一般我们无需设置,除非自己定义类调用) t.start() 线程准备就绪,等待CPU调度 t.getName() 获取线程的名称 t.setName() 设置线程的名称 t.name 获取或设置线程的名称 t.is_alive() 判断线程是否为激活状态 t.isAlive() 判断线程是否为激活状态 t.isDaemon() 判断是否为守护线程 t.setDaemon() 是否设置守护线程,True表示主线程不等待子线程全部完成就执行后面的代码,False默认值,标识主线程等待子线程全部执行完后继续执行后面的代码 threading.current_thread() 当前线程详细信息 threading.active_count() 当前活跃线程数 threading.get_ident 获得线程号
threading.enumerate() 当前执行的线程列表
线程执行顺序
主线程启动子线程后,两者之间运行是并行的,默认主线程不会等待子线程运行结束,创建完成后继续往下执行,执行完后等待子线程全部执行完后退出程序。
import threading import time def run(*args): # 线程要运行的函数 print('test',args) time.sleep(3) print(args, 'is done') start_time = time.time() for i in range(3): t = threading.Thread(target=run, args=('t-%s' %i ,)) t.start() print("-------------------------------------------") print('run_time = ', time.time()- start_time) # test ('t-0',) # test ('t-1',) # test ('t-2',) # ------------------------------------------- # run_time = 0.0 # ('t-2',) is done # ('t-1',) is done # ('t-0',) is done
加入join,主线程会等待子线程执行完毕后继续往下执行,如果主线程需要子线程的返回结果,可以使用join。
import threading import time def run(*args): # 线程要运行的函数 print('running',args) time.sleep(3) print(args, 'is done') start_time = time.time() thread_pool = [] for i in range(2): t = threading.Thread(target=run, args=('t-%s' %i ,)) t.start() thread_pool.append(t) for i in thread_pool: i.join() print('------------------------------------------') print('run_time = ', time.time()- start_time) # running ('t-0',) # running ('t-1',) # ('t-1',) is done # ('t-0',) is done # ------------------------------------------ # run_time = 3.0156474113464355
join
join 参数:timeout 有n个设置join的子线程,就等待n倍timeout, 默认一直等待执行完毕
当没有设置守护线程时,主线程等待 N 倍timeout后继续往下执行,主线程执行完毕,子线程依然可以继续执行,执行完毕后退出程序;对于守护线程则是到时间就kill子线程。
1 import threading 2 import time 3 def run(*args): # 线程要运行的函数 4 print('running',args) 5 time.sleep(3) 6 print(args, 'is done') 7 8 start_time = time.time() 9 thread_pool = [] 10 for i in range(5): 11 t = threading.Thread(target=run, args=('t-%s' %i ,)) 12 t.setDaemon(True) 13 t.start() 14 thread_pool.append(t) 15 16 for i in thread_pool: 17 i.join(0.5) # 有n个线程就等待n 倍的timeout 18 19 print('------------------------------------------') 20 21 print('run_time = ', time.time()- start_time) 22 23 # 24 # running ('t-0',) 25 # running ('t-1',) 26 # running ('t-2',) 27 # running ('t-3',) 28 # running ('t-4',) 29 # ------------------------------------------ 30 # run_time = 2.531254768371582 31 # 32 # Process finished with exit code 0
1 import threading 2 import time 3 def run(*args): # 线程要运行的函数 4 print('running',args) 5 time.sleep(3) 6 print(args, 'is done') 7 8 start_time = time.time() 9 thread_pool = [] 10 for i in range(5): 11 t = threading.Thread(target=run, args=('t-%s' %i ,)) 12 t.setDaemon(True) 13 t.start() 14 thread_pool.append(t) 15 16 for i in thread_pool: 17 i.join(0.6) # 有n个线程就等待n 倍的timeout 18 19 print('------------------------------------------') 20 21 print('run_time = ', time.time()- start_time) 22 23 # #running ('t-0',) 24 # running ('t-1',) 25 # running ('t-2',) 26 # running ('t-3',) 27 # running ('t-4',) 28 # ('t-2',) is done 29 # ('t-1',) is done 30 # ('t-0',) is done 31 # ('t-4',) is done 32 # ('t-3',) is done 33 # ------------------------------------------ 34 # run_time = 3.021475076675415 35 # 36 # Process finished with exit code 0
守护线程
为主线程服务,主线程退出,不必等待守护线程的结束。
t.setDaemon(True) 把当前线程设置成守护线程 ,在t.start()之前设置
import threading import time def run(*args): # 线程要运行的函数 print('test',args) time.sleep(3) print(args, 'is done') start_time = time.time() for i in range(2): t = threading.Thread(target=run, args=('t-%s' %i ,)) t.setDaemon(True) t.start() print("-------------------------------------------") print('run_time = ', time.time()- start_time) # # test ('t-0',) # test ('t-1',) # ------------------------------------------- # run_time = 0.0 # Process finished with exit code 0
由守护线程创建的子线程为守护线程,可以通过t.isDaemon来判断。
import time import threading def run(n): print('[%s]------running---- ' % n) time.sleep(1) print('[%s]------done---- ' % n) def main(): for i in range(2): t = threading.Thread(target=run, args=[i, ]) t.start() print(i,t.isDaemon()) t.join() m = threading.Thread(target=main, args=[]) m.setDaemon(True) # 将main线程设置为Daemon线程,它做为程序主线程的守护线程,当主线程退出时,m线程也会退出,由m启动的其它子线程会同时退出,不管是否执行完任务 m.start() m.join(timeout=2) print("---main thread done----") # [0]------running---- # 0 True # [0]------done---- # [1]------running---- # 1 True # ---main thread done----
GIL
无论启多少个线程,有多少个cpu,Python在执行的时候会淡定的在同一时刻只允许一个线程运行。
GIL(Global Interpreter Lock)是在实现Python解析器(CPython)时所引入的一个概念,同样一段代码可以通过CPython,PyPy,Psyco等不同的Python执行环境来执行。像其中的JPython就没有GIL。然而因为CPython是大部分环境下默认的Python执行环境。GIL并不是Python的特性,Python完全可以不依赖于GIL。
GIL对python多线程的影响: http://www.dabeaz.com/python/UnderstandingGIL.pdf
线程锁(互斥锁Mutex)
线程锁保证同一时刻只有一个线程修改内存空间的同一数据,GIL保证同一时刻只有一个线程在运行。
多线程同时修改同一数据,可能会导致数据最终结果不准确。
import time import threading def addNum(): global num # 在每个线程中都获取这个全局变量 time.sleep(1) num -= 1 # 对此公共变量进行-1操作 print('%s--get num:%s:'%(threading.current_thread().name,num )) num = 5 # 设定一个共享变量 thread_list = [] for i in range(5): t = threading.Thread(target=addNum) t.start() thread_list.append(t) for t in thread_list: # 等待所有线程执行完毕 t.join() print('final num:', num)
如果需要多个线程去修改同一数据,则需要给数据加一个线程锁。python3.x不加锁也不会出问题,但是建议加。如果修改数据量比较大的话,容易产生串行。
import time import threading lock = threading.Lock() def addNum(): lock.acquire() global num # 在每个线程中都获取这个全局变量 time.sleep(1) num -= 1 # 对此公共变量进行-1操作 print('%s--get num:%s'%(threading.current_thread().name,num )) lock.release() num = 5 # 设定一个共享变量 thread_list = [] for i in range(5): t = threading.Thread(target=addNum) t.start() thread_list.append(t) for t in thread_list: # 等待所有线程执行完毕 t.join() print('final num:', num)
递归锁
一个锁里面包含了子锁。
import time import threading lock = threading.RLock() def run2(): global num2 lock.acquire() num2 -= 1 lock.release() def addNum(): global num1 # 在每个线程中都获取这个全局变量 lock.acquire() run2() time.sleep(1) num1 -= 1 # 对此公共变量进行-1操作 print('%s--get num1:%s,num2:%s'%(threading.current_thread().name,num1,num2)) lock.release() num1, num2 = 5, 9 # 设定一个共享变量 thread_list = [] for i in range(5): t = threading.Thread(target=addNum) t.start() thread_list.append(t) while threading.active_count() != 1: print(threading.active_count()) time.sleep(1) else: print('----all threads done---') print('final num:', num1, num2)
信号量:Semaphore
互斥锁允许同一时刻只有一个线程修改数据,Semaphore 允许同一时刻运行一定数量的线程。
import time import threading semaphore = threading.BoundedSemaphore(3) def addNum(): semaphore.acquire() time.sleep(1) print('%s---running'%threading.current_thread().name) semaphore.release() for i in range(10): t = threading.Thread(target=addNum) t.start() while threading.active_count() != 1: pass else: print('----all threads done---')
Events
红绿灯,一个线程充当交通指挥灯,多个线程充当车辆,按照红灯停绿灯行的规则。注意event.set, event.clear放在打印灯和车的状态位置,否则容易出现红灯车也在跑的情况。
Event对象通过设置/清除标志位来实现和其他线程的同步,例如交通灯来修改Event的标志位来控制车辆线程的状态。
import threading, time, random events = threading.Event() def lighter(): if not events.isSet(): events.set() # 初始化绿灯Event set counter = 0 while True: if counter < 5: print('