多线程
-
程序:一堆代码以文本形式存入一个文档
-
进程:程序运行的一个状态
- 包含地址空间、内容、数据栈等
- 每个进程由自己完全独立的运行环境,多进程共享数据是一个问题
-
线程
- 一个进程的独立运行片段,一个进程可以由有多个线程
- 轻量化的进程
- 一个进程的多个线程间共享数据和上下文运行环境
- 共享互斥问题
-
全局解释器锁(GIL)
- Python代码的执行是由python虚拟机进行控制
- 在主循环中只能拥有一个控制线程在执行
-
Python包
- thread:有问题,不好用,Python3改成了_thread
- threading:通行的包
# 不使用线程,顺序执行,程序运行时间长 # 利用time函数,生成两个函数,顺序调用,计算总的运行时间 import time def loop1(): # ctime得到当前时间 print("Start loop 1 at:",time.ctime()) # 睡眠一定时间 time.sleep(4) print("End loop 1 at:",time.ctime()) def loop2(): # ctime得到当前时间 print("Start loop 2 at:",time.ctime()) # 睡眠一定时间 time.sleep(2) print("End loop 2 at:",time.ctime()) def main(): print("Starting at:",time.ctime()) loop1() loop2() print("All done at:",time.ctime()) if __name__ == "__main__": main()
输出结果为:
Starting at: Tue Nov 27 20:35:53 2018 Start loop 1 at: Tue Nov 27 20:35:53 2018 End loop 1 at: Tue Nov 27 20:35:57 2018 Start loop 2 at: Tue Nov 27 20:35:57 2018 End loop 2 at: Tue Nov 27 20:35:59 2018 All done at: Tue Nov 27 20:35:59 2018
_thread
- 改用多线程,缩短总时间,使用_thread
import time import _thread as thread def loop1(): # ctime得到当前时间 print("Start loop 1 at:",time.ctime()) # 睡眠一定时间 time.sleep(4) print("End loop 1 at:",time.ctime()) def loop2(): # ctime得到当前时间 print("Start loop 2 at:",time.ctime()) # 睡眠一定时间 time.sleep(2) print("End loop 2 at:",time.ctime()) def main(): print("Starting at:",time.ctime()) thread.start_new_thread(loop1,()) thread.start_new_thread(loop2,()) print("All done at:",time.ctime()) if __name__ == "__main__": main() while True: time.sleep(1)
主线程已经结束而子线程仍在继续运行,输出结果为:
Starting at: Tue Nov 27 21:42:32 2018 All done at: Tue Nov 27 21:42:32 2018 Start loop 1 at: Tue Nov 27 21:42:32 2018 Start loop 2 at: Tue Nov 27 21:42:32 2018 End loop 2 at: Tue Nov 27 21:42:34 2018 End loop 1 at: Tue Nov 27 21:42:36 2018
- 使用多线程传参数
# 使用多线程传递参数 import time import _thread as thread def loop1(in1): # ctime得到当前时间 print("Start loop 1 at:", time.ctime()) print("我是参数 ", in1) time.sleep(4) print("End loop 1 at:", time.ctime()) def loop2(in1, in2): # ctime得到当前时间 print("Start loop 2 at:", time.ctime()) print("我是参数 ", in1, "和参数 ", in2) time.sleep(2) print("End loop 2 at:", time.ctime()) def main(): print("Starting at:", time.ctime()) # 启动多线程的意思是用多线程去执行某个函数 # 启动多线程函数为start_new_thread # 参数两个,一个是需要运行的函数名,第二是函数的参数作为元组使用,为空则使用空元组 # 注意:如果函数只有一个参数,需要参数后面加一逗号 thread.start_new_thread(loop1, ("往",)) thread.start_new_thread(loop2, ("生", "经")) print("All done at:", time.ctime()) if __name__ == "__main__": main() while True: time.sleep(10)
执行结果如下:
Starting at: Tue Nov 27 21:50:16 2018 All done at: Tue Nov 27 21:50:16 2018 Start loop 1 at: Tue Nov 27 21:50:16 2018 我是参数 往 Start loop 2 at: Tue Nov 27 21:50:16 2018 我是参数 生 和参数 经 End loop 2 at: Tue Nov 27 21:50:18 2018 End loop 1 at: Tue Nov 27 21:50:20 2018
threading
-
直接使用threading.Thread生成Thread实例
- t = threading.Thread(target=xxx,args=(xxx,))
- t.start() 启动多线程
- t.join() 等待多线程执行完成
# threading的使用 import time import threading def loop1(in1): # ctime得到当前时间 print("Start loop 1 at:",time.ctime()) print("我是参数 ",in1) time.sleep(4) print("End loop 1 at:",time.ctime()) def loop2(in1,in2): # ctime得到当前时间 print("Start loop 2 at:",time.ctime()) print("我是参数 ",in1+"和参数 ",in2) time.sleep(2) print("End loop 2 at:",time.ctime()) def main(): print("Starting at:",time.ctime()) t1 = threading.Thread(target=loop1,args=("往",)) t1.start() t2 = threading.Thread(target=loop2,args=("生","经")) t2.start() print("All done at:",time.ctime()) if __name__ == "__main__": main() while True: time.sleep(10)
输出结果:
Starting at: Tue Nov 27 21:56:22 2018 Start loop 1 at: Tue Nov 27 21:56:22 2018 我是参数 往 Start loop 2 at: Tue Nov 27 21:56:22 2018 All done at: Tue Nov 27 21:56:22 2018 我是参数 生和参数 经 End loop 2 at: Tue Nov 27 21:56:24 2018 End loop 1 at: Tue Nov 27 21:56:26 2018
-
守护线程daemon
- 如果在程序中将子线程设置为守护线程,则子线程会在主线程结束的时候自动退出
- 一般认为,守护线程不重要或者不允许离开主线程独立运行
- 守护线程能否有效跟环境相关
# 非守护线程 import time import threading def fun(): print("Start fun") time.sleep(2) print("End fun") print("Main thread") t1 = threading.Thread(target=fun,args=()) t1.start() time.sleep(1) print("Main thread end")
输出结果:
Main thread Start fun Main thread end End fun
# 守护线程 import time import threading def fun(): print("Start fun") time.sleep(2) print("End fun") print("Main thread") t1 = threading.Thread(target=fun, args=()) # 设置守护线程方法,必须在start之前设置,否则无效 t1.setDaemon(True) t1.start() time.sleep(1) print("Main thread end")
输出结果为:
Main thread Start fun Main thread end
-
线程常用属性
- threading.currentThread 返回当前线程变量
- threading.enumerate 返回一个包含正在运行的线程的list,正在运行的线程指的是线程启动后,结束前
- threading.activeCount 返回正在运行的线程数量,效果跟len(threading.enumerate)相同
- thr.setName 给线程设置名字
- thr.getName 得到线程的名字
import time import threading def loop1(): # ctime得到当前时间 print("Start loop 1 at:", time.ctime()) time.sleep(4) print("End loop 1 at:", time.ctime()) def loop2(): # ctime得到当前时间 print("Start loop 2 at:", time.ctime()) time.sleep(2) print("End loop 2 at:", time.ctime()) def loop3(): # ctime得到当前时间 print("Start loop 3 at:", time.ctime()) time.sleep(5) print("End loop 3 at:", time.ctime()) def main(): print("Starting at:", time.ctime()) t1 = threading.Thread(target=loop1, args=()) t1.setName("THR_1") t1.start() t2 = threading.Thread(target=loop2, args=()) t2.setName("THR_2") t2.start() t3 = threading.Thread(target=loop3, args=()) t3.setName("THR_3") t3.start() time.sleep(3) for thr in threading.enumerate(): print("正在运行的线程的名字是:{0}".format(thr.getName())) print("正在运行的子线程数量为:{0}".format(threading.activeCount())) print("All done at:", time.ctime()) if __name__ == "__main__": main() while True: time.sleep(10)
程序结果为:
Starting at: Tue Nov 27 22:01:56 2018 Start loop 1 at: Tue Nov 27 22:01:56 2018 Start loop 2 at: Tue Nov 27 22:01:56 2018 Start loop 3 at: Tue Nov 27 22:01:56 2018 End loop 2 at: Tue Nov 27 22:01:58 2018 正在运行的线程的名字是:MainThread 正在运行的线程的名字是:THR_1 正在运行的线程的名字是:THR_3 正在运行的子线程数量为:3 All done at: Tue Nov 27 22:01:59 2018 End loop 1 at: Tue Nov 27 22:02:00 2018 End loop 3 at: Tue Nov 27 22:02:01 2018
-
直接继承来自threading.Thread
- 直接继承Thread
- 重写run函数
- 类实例可以直接运行
import threading import time # 类需要继承threading.Thread class MyThread(threading.Thread): def __init__(self,arg): super(MyThread,self).__init__() self.arg = arg # 必须重写run函数,run函数代表的是真正执行的功能 def run(self): time.sleep(2) print("The args for this class is {0}".format(self.arg)) for i in range(5): t = MyThread(i) t.start() t.join() print("Main thread is done!!!!!!")
结果为:
The args for this class is 0 The args for this class is 1 The args for this class is 2 The args for this class is 3 The args for this class is 4 Main thread is done!!!!!!
共享变量
-
共享变量:当多个线程同时访问一个变量的时候,会产生共享变量的问题
-
解决方法:锁
-
锁(Lock):
- 是一个标志,表示一个线程在占用一些资源
- 使用方法
- 上锁
- 使用共享资源
- 解锁,释放锁
- 锁谁:哪个资源需要多个线程共享,就锁哪个
- 理解:这个锁就类似于一个令牌,谁拿到这个令牌谁有使用权
# 此程序使用锁 import threading # 定义变量 sum = 1 loopSum = 10000 #调用锁 lock = threading.Lock() def myAdd(): global sum,loopSum for i in range(1,loopSum): # 上锁 lock.acquire() sum += 1 # 解锁 lock.release() def myMinu(): global sum,loopSum for i in range(1,loopSum): lock.acquire() sum -= 1 lock.release() if __name__ == "__main__": print("Starting...{0}".format(sum)) t1 = threading.Thread(target=myAdd(),args=()) t2 = threading.Thread(target=myMinu(),args=()) t1.start() t2.start() t1.join() t2.join() print("Done... {0}".format(sum))
-
线程安全问题
- 如果一个资源/变量,它对于多线程来讲,不使用锁也不会引起任何问题,则称为线程安全
- 线程不安全变量类型:list,set,dict
- 线程安全变量类型:queue
-
生产者消费者问题
- 一个模型,可以用来搭建消息队列
- queue是一个用来存放变量的数据结构,特点是先进后出,内部元素排队,可以理解成一个特殊的list
- queue的使用
#encoding = utf-8 import threading import time #python 2 #from Queue import Queue #python3 import queue class Producter(threading.Thread): def run(self): global queue count = 0 while True: #qsize返回queue内容长度 if queue.qsize()<1000: for i in range(100): count = count+1 msg = "生产产品" + str(count) #put是往queue中放入一个值 queue.put(msg) print(msg) time.sleep(0.5) class Cousumer(threading.Thread): def run(self): global queue while True: if queue.qsize() > 100: for i in range(3): # get是从queue中取出一个值 msg = self.name + "消费了" + queue.get() print(msg) time.sleep(1) if __name__ == "__main__": queue = queue.Queue() for i in range(500): queue.put("初始产品"+str(i)) for i in range(2): p = Producter() p.start() for i in range(5): c = Cousumer() c.start()
-
死锁问题
- 函数1申请了锁1,函数2申请了锁2,函数1未释放锁1申请锁2,函数2未释放锁2申请锁1,这便陷入了死锁。
- 解决方法:程序设计过程中格外注意锁的使用,一定要等释放了锁才去申请锁;或者设定申请锁的等待时间,等不到锁就跳过。
-
Semaphore()
- 允许一个资源最多由几个多线程同时使用
import threading import time # 参数定义最多3个线程同时使用线程 semaphore = threading.Semaphore(3) def func(): if semaphore.acquire(): for i in range(5): print(threading.current_thread().getName() + ' get semaphore') time.sleep(15) semaphore.release() print(threading.current_thread().getName() + ' release semaphore') for i in range(8): t1 = threading.Thread(target=func) t1.start()
-
threading.Timer
- Timer是利用多线程在指定时间后启动一个功能
-
可重入锁(RLock())
- 一个锁,可以被一个线程多次申请
- 主要解决递归调用的时候需要申请锁的情况
import threading import time class MyThread(threading.Thread): def run(self): global num time.sleep(1) if mutex.acquire(1): num = num + 1 msg = self.name+ ' set num to ' + str(num) print(msg) mutex.acquire() mutex.release() mutex.release() num = 0 #重入锁 mutex = threading.RLock() def testTh(): for i in range(5): t = MyThread() t.start()
如果此处使用Lock,普通锁,程序无法执行报错,使用RLock,结果如下
Thread-6 set num to 1 Thread-7 set num to 2 Thread-8 set num to 3 Thread-9 set num to 4 Thread-10 set num to 5
线程替代方案
- subprocess
- 完全跳过线程,使用进程
- 是派生进程的主要替代方案
- python2.4后引入
- multiprocessing
- 使用threading接口派生,使用子线程
- 允许多核或者多CPU派生进程,接口跟threading非常相似
- python2.6
- concurrent.futures
- 新的异步执行操作
- 任务级别的操作
- python3.2后引入
多进程
-
进程间通讯(InterprocessCommunication,IPC)
-
进程之间无任何共享状态
-
进程的创建
- 直接生成Process实例对象
直接实例化 import multiprocessing from time import sleep,ctime def clock(interval): while True: print("The time is {0}".format(ctime())) sleep(interval) if __name__ == "__main__": p = multiprocessing.Process(target=clock,args=(5,)) p.start() while True: print("Sleep...") sleep(1)
- 派生子类
import multiprocessing from time import sleep,ctime class ClockProcess(multiprocessing.Process): ''' 两个函数比较重要: 1. init构造函数 2. run ''' def __init__(self,interval): super().__init__() self.interval = interval def run(self): while True: print("The time is {0}".format(ctime())) sleep(self.interval) if __name__ == "__main__": p = ClockProcess(3) p.start() while True: print("Sleep...") sleep(1)
- 直接生成Process实例对象
-
在os中查看pid、ppid以及关系
from multiprocessing import Process import os def info(title): print(title) print('module name:',__name__) #得到父进程的id print("parent process id:",os.getppid()) #得到自身进程的id print("process is:",os.getpid()) def f(name): info("function-1") print("hello",name) if __name__ == '__main__': info('main line') p = Process(target=f,args=('cher',)) p.start()
程序结果:
main line module name: __main__ parent process id: 4780 process is: 4776 function-1 module name: __mp_main__ parent process id: 4776 process is: 5772 hello cher
-
生产消费者模型
-
JoinableQueue的使用,consumer函数依据队列queue从仓库中取出产品,producer函数生产产品根据队列queue放入仓库。
import multiprocessing from time import sleep,ctime def consumer(input_q): print("Into consumer:{0}".format(ctime())) while True: #处理项 item = input_q.get() print("pull",item,"out of q") #发出信号通知任务完成 input_q.task_done() # 此句未被执行,因为q.join()收集到四个task_done()信号后,主进程启动,未等到print此句完 # print("Out of consumer:{0}".format(ctime())) def producer(sequence,output_q): print("Into producer:",format(ctime())) for item in sequence: output_q.put(item) print("put",item,"into q") print("Out of producer:",format(ctime())) #建立进程 if __name__ == "__main__": q = multiprocessing.JoinableQueue() #运行消费者进程 cons_p = multiprocessing.Process(target=consumer,args=(q,)) cons_p.daemon = True cons_p.start() #生产多个项,sequence代表要发送给消费者的项序列 #在实践中,这可能是生成器的输出或通过一些其他方式生产出来 sequence = [1,2,3,4] producer(sequence,q) #等待所有项被处理 q.join()
程序运行结果:
Into producer: Wed Nov 28 22:00:41 2018 put 1 into q put 2 into q put 3 into q put 4 into q Out of producer: Wed Nov 28 22:00:41 2018 Into consumer:Wed Nov 28 22:00:41 2018 pull 1 out of q pull 2 out of q pull 3 out of q pull 4 out of q
-
队列queue中哨兵的使用,即控制生产消费者关系是否继续维持
import multiprocessing from time import sleep,ctime #设置哨兵 def consumer(input_q): print("Into consumer:{0}".format(ctime())) while True: item = input_q.get() if item is None: break print("pull",item,"out of q") # 此句执行完成,在转入主线程 print("Out of consumer:{0}".format(ctime())) def producer(sequence,output_q): print("Into producer:",format(ctime())) for item in sequence: output_q.put(item) print("put",item,"into q") print("Out of producer:",format(ctime())) if __name__ == "__main__": q = multiprocessing.Queue() #运行消费者进程 cons_p1 = multiprocessing.Process(target=consumer,args=(q,)) cons_p1.start() sequence = [1,2,3,4] producer(sequence,q) #有多少个消费者放置多少个None q.put(None) cons_p1.join()
运行结果:
Into producer: Wed Nov 28 22:21:56 2018 put 1 into q put 2 into q put 3 into q put 4 into q Out of producer: Wed Nov 28 22:21:56 2018 Into consumer:Wed Nov 28 22:21:56 2018 pull 1 out of q pull 2 out of q pull 3 out of q pull 4 out of q Out of consumer:Wed Nov 28 22:21:56 2018
-