一、线程
线程是操作系统能够进行运算调度的最小单位,是一串指令的集合。
它被包含在进程之中,是进程中的实际运作单位。
一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
所有在同一进程里的线程是共享同一块内存空间的。
运行多线程需要频繁的cpu上下文切换。
注:cpu内一个核数只能同时运行一个线程,所以多核cpu同时可以运行多个线程。
在Python中,即使是多核cpu,同时运行的线程也只有一个,Python语言设计之初就不支持多核。
所以在Python程序中,启用越多的线程,程序不一定运行的就很快,因为cpu要进行大量的上下文切换,反而消耗时间;
GIL全局解释锁保障线程的上下文关系,保障当前只有一个线程在运行,与lock数据加锁无关。
1、建立线程(threading模块)
线程创建有2种方式:
直接调用
1 #!/user/bin/env ptyhon 2 # -*- coding:utf-8 -*- 3 # Author: VisonWong 4 5 import threading, time 6 7 def run(n): 8 print('Task',n) 9 time.sleep(2) 10 11 12 if __name__ == "__main__": 13 14 t1= threading.Thread(target=run,args=('t1',)) 15 t2= threading.Thread(target=run,args=('t2',)) 16 17 #两个同时执行,然后等待两秒程序结束 18 t1.start() 19 t2.start() 20 21 22 #程序输出 23 # Task t1 24 # Task t2
继承式调用
1 #!/user/bin/env ptyhon 2 # -*- coding:utf-8 -*- 3 # Author: VisonWong 4 5 import threading,time 6 7 class MyThread(threading.Thread): 8 9 def __init__(self,num): 10 # threading.Thread.__init__(self) 11 super(MyThread, self).__init__() 12 self.num = num 13 14 def run(self): #定义每个线程要运行的函数 15 print('running task',self.num) 16 time.sleep(2) 17 18 if __name__ == '__main__': 19 #两个同时执行,然后等待两秒程序结束 20 t1 = MyThread('t1') 21 t2 = MyThread('t2') 22 23 t1.start() 24 t2.start() 25 26 27 # 程序输出 28 # running task t1 29 # running task t2
2、多线程的默认情况
当一个进程启动之后,会默认产生一个主线程,因为线程是程序执行流的最小单元,当设置多线程时,主线程会创建多个子线程。
在python中,默认情况下(其实就是setDaemon(False)),主线程执行完自己的任务以后,就退出了,此时子线程会继续执行自己的任务,直到自己的任务结束。
1 #!/user/bin/env ptyhon 2 # -*- coding:utf-8 -*- 3 # Author: VisonWong 4 5 6 import threading 7 import time 8 9 def run(): 10 time.sleep(2) 11 print('当前线程的名字是: ', threading.current_thread()) 12 # time.sleep(2) 13 14 15 if __name__ == '__main__': 16 17 start_time = time.time() 18 19 print('这是主线程:', threading.current_thread()) 20 thread_list = [] 21 for i in range(5): 22 t = threading.Thread(target=run) 23 thread_list.append(t) 24 25 for t in thread_list: 26 t.start() 27 28 29 print('主线程结束!' , threading.current_thread().name,'done') 30 print('一共用时:', time.time()-start_time) 31 32 33 # 这是主线程: <_MainThread(MainThread, started 7124)> 34 # 主线程结束! MainThread done 35 # 一共用时: 0.002000093460083008 36 # 当前线程的名字是: <Thread(Thread-3, started 3560)> 37 # 当前线程的名字是: <Thread(Thread-4, started 7256)> 38 # 当前线程的名字是: <Thread(Thread-2, started 2164)> 39 # 当前线程的名字是: <Thread(Thread-1, started 5452)> 40 # 当前线程的名字是: <Thread(Thread-5, started 6780)>
1、我们的计时是对主线程计时,主线程结束,计时随之结束,打印出主线程的用时。
2、主线程的任务完成之后,主线程随之结束,子线程继续执行自己的任务,直到全部的子线程的任务全部结束,程序结束。
3、started 后跟的是当前线程的识别号,可通过 threading.get_ident() 获得。
3、守护线程Daemon
当我们使用setDaemon(True)方法,设置子线程为守护线程时,主线程一旦执行结束,则全部线程全部被终止执行。
可能出现的情况就是,子线程的任务还没有完全执行结束,就被迫停止。
1 #!/user/bin/env ptyhon 2 # -*- coding:utf-8 -*- 3 # Author: VisonWong 4 5 6 import threading 7 import time 8 9 def run(): 10 11 time.sleep(2) 12 print('当前线程的名字是: ', threading.current_thread().name) 13 time.sleep(2) 14 15 16 if __name__ == '__main__': 17 18 start_time = time.time() 19 20 print('这是主线程:', threading.current_thread().name) 21 22 thread_list = [] 23 for i in range(5): 24 t = threading.Thread(target=run) 25 thread_list.append(t) 26 27 for t in thread_list: 28 t.setDaemon(True) 29 t.start() 30 31 print('主线程结束了!' , threading.current_thread().name) 32 print('一共用时:', time.time()-start_time) 33 34 35 # 这是主线程: MainThread 36 # 主线程结束了! MainThread 37 # 一共用时: 0.002000093460083008
非常明显的看到,主线程结束以后,子线程还没有来得及执行,整个程序就退出了。
4、join方法
join所完成的工作就是线程同步, 等待被join的线程执行完后,其他线程再继续执行。
1 #!/user/bin/env ptyhon 2 # -*- coding:utf-8 -*- 3 # Author: VisonWong 4 5 import threading 6 import time 7 8 def run(): 9 10 time.sleep(2) 11 print('当前线程的名字是: ', threading.current_thread().name) 12 time.sleep(2) 13 14 15 if __name__ == '__main__': 16 17 start_time = time.time() 18 19 print('这是主线程:', threading.current_thread().name) 20 thread_list = [] 21 for i in range(5): 22 t = threading.Thread(target=run) 23 thread_list.append(t) 24 25 for t in thread_list: 26 t.setDaemon(True) 27 t.start() 28 29 for t in thread_list: 30 t.join() 31 32 print('主线程结束了!' , threading.current_thread().name) 33 print('一共用时:', time.time()-start_time) 34 35 36 # 这是主线程: MainThread 37 # 当前线程的名字是: Thread-2 38 # 当前线程的名字是: Thread-1 39 # 当前线程的名字是: Thread-4 40 # 当前线程的名字是: Thread-3 41 # 当前线程的名字是: Thread-5 42 # 主线程结束了! MainThread 43 # 一共用时: 4.0142295360565186
注:计算所有线程运行的时间,待所有线程运行完后,运行主程序,计算运行时间。
join有一个timeout参数:
1、当设置守护线程时,含义是主线程对于子线程等待timeout的时间将会杀死该子线程,最后退出程序。
简单的来说,就是给每个子线程一个timeout的时间,让他去执行,时间一到,不管任务有没有完成,直接杀死。
2、没有设置守护线程时,主线程将会等待timeout这样的一段时间,时间一到,主线程结束,但是并没有杀死子线程,子线程依然可以继续执行,直到子线程全部结束,程序退出。
5、Mutex 线程锁
一个进程下可以启动多个线程,多个线程共享父进程的内存空间,也就意味着每个线程可以访问同一份数据,此时,如果2个线程同时要修改同一份数据,会出现什么状况?
1 #!/user/bin/env ptyhon 2 # -*- coding:utf-8 -*- 3 # Author: VisonWong 4 5 import time 6 import threading 7 8 9 def addNum(): 10 global num # 在每个线程中都获取这个全局变量 11 print('--get num:', num) 12 time.sleep(1) 13 num -= 1 # 对此公共变量进行-1操作 14 print('--after num:',num) 15 16 17 num = 10 # 设定一个共享变量 18 thread_list = [] 19 for i in range(10): 20 t = threading.Thread(target=addNum) 21 t.start() 22 thread_list.append(t) 23 24 for t in thread_list: # 等待所有线程执行完毕 25 t.join() 26 27 print('final num:', num) 28 29 30 # --get num: 10 31 # --get num: 10 32 # --get num: 10 33 # --get num: 10 34 # --get num: 10 35 # --get num: 10 36 # --get num: 10 37 # --get num: 10 38 # --get num: 10 39 # --get num: 10 40 # --after num: 9 41 # --after num: 8 42 # --after num: 7 43 # --after num: 6 44 # --after num: 5 45 # --after num: 4 46 # --after num: 3 47 # --after num: 2 48 # --after num: 1 49 # --after num: 0 50 # final num: 0
正常来讲,这个num结果应该是0, 但在python 2.7上多运行几次,会发现,最后打印出来的num结果不总是0,为什么每次运行的结果不一样呢?
假设你有A,B两个线程,此时都 要对num 进行减1操作, 由于2个线程是并发同时运行的,所以2个线程很有可能同时拿走了num=100这个初始变量交给cpu去运算。
当A线程去处完的结果是99,但此时B线程运算完的结果也是99,两个线程同时CPU运算的结果再赋值给num变量后,结果就都是99。
因此每个线程在要修改公共数据时,为了避免自己在还没改完的时候别人也来修改此数据,可以给这个数据加一把锁, 这样其它线程想修改此数据时就必须等待你修改完毕并把锁释放掉后才能再访问此数据。
对程序加锁:
1 #!/user/bin/env ptyhon 2 # -*- coding:utf-8 -*- 3 # Author: VisonWong 4 5 import time 6 import threading 7 8 9 def addNum(): 10 global num # 在每个线程中都获取这个全局变量 11 print('--get num:', num) 12 time.sleep(1) 13 lock.acquire() # 修改数据前加锁 14 num -= 1 # 对此公共变量进行-1操作 15 lock.release() # 修改后释放 16 print('--after num:',num) 17 18 19 num = 10 # 设定一个共享变量 20 thread_list = [] 21 lock = threading.Lock() # 生成全局锁 22 for i in range(10): 23 t = threading.Thread(target=addNum) 24 t.start() 25 thread_list.append(t) 26 27 for t in thread_list: # 等待所有线程执行完毕 28 t.join() 29 30 print('final num:', num) 31 32 33 # --get num: 10 34 # --get num: 10 35 # --get num: 10 36 # --get num: 10 37 # --get num: 10 38 # --get num: 10 39 # --get num: 10 40 # --get num: 10 41 # --get num: 10 42 # --get num: 10 43 # --after num: 9 44 # --after num: 8 45 # --after num: 7 46 # --after num: 6 47 # --after num: 5 48 # --after num: 4 49 # --after num: 3 50 # --after num: 2 51 # --after num: 1 52 # --after num: 0 53 # final num: 0
Python已经有一个GIL来保证同一时间只能有一个线程来执行了,为什么这里还需要lock?
注意啦,这里的lock是用户级的lock,跟那个GIL没关系 ,具体我们通过下图来看一下。
虽然python有全局锁保证通过一时间只有一个线程进行,但有可能当前线程修改全局变量未完成而被挂起,换作另一个线程同时修改同一全局变量,就有可能出现修改不准确的现象。
6、递归锁Rlock
当一个大锁中还要再包含子锁的时候,如果再用threading.Lock的话,程序锁和钥匙会出现对不上的情况,这时候就需要用到递归锁。
1 #!/user/bin/env ptyhon 2 # -*- coding:utf-8 -*- 3 # Author: VisonWong 4 5 6 import threading 7 8 9 def run1(): 10 print("grab the first part data") 11 lock.acquire() 12 global num 13 num += 1 14 lock.release() 15 return num 16 17 18 def run2(): 19 print("grab the second part data") 20 lock.acquire() 21 global num2 22 num2 += 1 23 lock.release() 24 return num2 25 26 27 def run3(): 28 lock.acquire() 29 res = run1() 30 print('--------between run1 and run2-----') 31 res2 = run2() 32 lock.release() 33 print(res, res2) 34 35 36 if __name__ == '__main__': 37 38 num, num2 = 0, 0 39 thread_list = [] 40 lock = threading.RLock() 41 for i in range(10): 42 t = threading.Thread(target=run3) 43 t.start() 44 thread_list.append(t) 45 46 for t in thread_list: 47 t.join() 48 49 50 print('----all threads done---') 51 print(num, num2) 52 53 54 # grab the first part data 55 # --------between run1 and run2----- 56 # grab the second part data 57 # 1 1 58 # grab the first part data 59 # --------between run1 and run2----- 60 # grab the second part data 61 # 2 2 62 # grab the first part data 63 # --------between run1 and run2----- 64 # grab the second part data 65 # 3 3 66 # grab the first part data 67 # --------between run1 and run2----- 68 # grab the second part data 69 # 4 4 70 # grab the first part data 71 # --------between run1 and run2----- 72 # grab the second part data 73 # 5 5 74 # grab the first part data 75 # --------between run1 and run2----- 76 # grab the second part data 77 # 6 6 78 # grab the first part data 79 # --------between run1 and run2----- 80 # grab the second part data 81 # 7 7 82 # grab the first part data 83 # --------between run1 and run2----- 84 # grab the second part data 85 # 8 8 86 # grab the first part data 87 # --------between run1 and run2----- 88 # grab the second part data 89 # 9 9 90 # grab the first part data 91 # --------between run1 and run2----- 92 # grab the second part data 93 # 10 10 94 # ----all threads done--- 95 # 10 10
7、Semaphore信号量
Mutex 同时只允许一个线程更改数据。
而Semaphore是同时允许一定数量的线程更改数据 ,比如饭店有3个位置,那最多只允许3个人吃饭,后面的人只能等里面有人出来了才能再进去。
1 #!/user/bin/env ptyhon 2 # -*- coding:utf-8 -*- 3 # Author: VisonWong 4 5 import threading, time 6 7 8 def run(n): 9 semaphore.acquire() 10 time.sleep(1) 11 print("run the thread: %s " % n) 12 semaphore.release() 13 14 15 16 if __name__ == '__main__': 17 18 thread_list=[] 19 semaphore = threading.BoundedSemaphore(5) # 最多允许5个线程同时运行 20 for i in range(10): 21 t = threading.Thread(target=run, args=(i,)) 22 t.start() 23 thread_list.append(t) 24 for t in thread_list: 25 t.join() 26 27 print('----all threads done---') 28 29 30 # run the thread: 3 31 # run the thread: 2 32 # run the thread: 4 33 # run the thread: 1 34 # run the thread: 0 35 # run the thread: 5 36 # run the thread: 9 37 # run the thread: 6 38 # run the thread: 8 39 # run the thread: 7 40 # ----all threads done---
8、Event
通过Event来实现两个或多个线程间的交互,下面是一个红绿灯的例子,即起动一个线程做交通指挥灯,生成几个线程做车辆,车辆行驶按红灯停,绿灯行的规则。
1 #!/user/bin/env ptyhon 2 # -*- coding:utf-8 -*- 3 # Author: VisonWong 4 5 import threading, time 6 7 def light(): 8 count = 0 9 while True: 10 if count < 10: # 红灯 11 print("