第十章:Python多线程
大纲:
多线程理论
开启线程
线程相关属性
守护线程
死所和递归锁
信号量
事件Event
进程池池与线程池concurrent.futures
一、多线程理论理论
什么是线程
进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合),而线程才是cpu上的执行单位。
多线程(即多个控制线程)的概念是,在一个进程中存在多个控制线程,多个控制线程共享该进程的地址空间,相当于一个车间内有多条流水线,都共用一个车间的资源。
线程的创建开销小
创建进程的开销要远大于线程?
如果我们的软件是一个工厂,该工厂有多条流水线,流水线工作需要电源,电源只有一个即cpu(单核cpu)
一个车间就是一个进程,一个车间至少一条流水线(一个进程至少一个线程)
创建一个进程,就是创建一个车间(申请空间,在该空间内建至少一条流水线)
而建线程,就只是在一个车间内造一条流水线,无需申请空间,所以创建开销小
线程与进程的区别
1、线程共享创建它的进程的地址空间;进程有自己的地址空间。
2、线程直接访问进程的数据段;进程拥有父进程的数据段的自身副本。
3、线程可以直接与其他线程的过程;过程必须使用进程间通信与兄弟姐妹的过程。
4、很容易创建新线程;新进程需要重复父进程。
5、线程可以对相同进程的线程进行相当的控制;进程只能对子进程进行控制。
6、对主线程的更改(取消、优先级更改等)可能影响进程的其他线程的行为;对父进程的更改不会影响子进程。
为何要用多线程
多线程指的是,在一个进程中开启多个线程,简单的讲:如果多个任务共用一块地址空间,那么必须在一个进程内开启多个线程。详细的讲分为4点:
1. 多线程共享一个进程的地址空间
2. 线程比进程更轻量级,线程比进程更容易创建可撤销,在许多操作系统中,创建一个线程比创建一个进程要快10-100倍,在有大量线程需要动态和快速修改时,这一特性很有用
3. 若多个线程都是cpu密集型的,那么并不能获得性能上的增强,但是如果存在大量的计算和大量的I/O处理,拥有多个线程允许这些活动彼此重叠运行,从而会加快程序执行的速度。
4. 在多cpu系统中,为了最大限度的利用多核,可以开启多个线程,比开进程开销要小的多。(这一条并不适用于python)
二、开启线程
1、使用Thread使用线程
# 1、使用Thread使用线程 from threading import Thread # from multiprocessing import Process def task(): print('==>is running.') if __name__=='__main__': t=Thread(target=task) # p=Process(target=task) t.start() # p.start() print('主线程')
2、继承Thread类使用线程
from threading import Thread # from multiprocessing import Process class MyThread(Thread): def __init__(self,name): super().__init__() self.name=name def run(self): print('==>is running.') if __name__=='__main__': t=MyThread('sheng') # p=Process(target=task) t.start() # p.start() print('主线程')
三、线程相关属性
1、在同一个主进程下线程的pid都相同,子进程不相同
from threading import Thread from multiprocessing import Process import os def task(): print('%s is running' %os.getpid()) if __name__ == '__main__': t1=Thread(target=task,) t2=Thread(target=task,) # t1=Process(target=task,) # t2=Process(target=task,) t1.start() t2.start() print('主',os.getpid())
2、多线程共享同一个资源
from threading import Thread n=100 def work(): global n n=0 if __name__=='__main__': t=Thread(target=work) t.start() t.join() print(n) #0
例[1]:模拟一个编辑器并发实现写入格式化和保存:
from threading import Thread msg_l=[] format_msg_l=[] def work(): while True: msg=input('>>').strip() if not msg:continue msg_l.append(msg) def format(): while True: if msg_l: data=msg_l.pop() format_msg_l.append(data.upper()) def save(): while True: if format_msg_l: data=format_msg_l.pop() with open('db.txt','a') as f: f.write(data) if __name__=='__main__': t1=Thread(target=work) t2=Thread(target=format) t3=Thread(target=save) t1.start() t2.start() t3.start()
3、Thread对象的其他属性或方法
from threading import Thread,active_count,enumerate def task(): print('==>is running ') if __name__=='__main__': t=Thread(target=task) t.start() print(t.is_alive()) # t.join() print(t.is_alive()) #判断t是否存活 print(t.getName()) print(active_count()) #返回或者的线程数量 print(enumerate()) #查看活跃线程的对象 print('main')
四、守护线程
无论是进程还是线程,都遵循:守护xxx会等待主xxx运行完毕后被销毁
需要强调的是:运行完毕并非终止运行
#1.对主进程来说,运行完毕指的是主进程代码运行完毕 #2.对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕
详细解释:
#1 主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收),然后主进程会一直等非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束, #2 主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,而进程必须保证非守护线程都运行完毕后才能结束。
#再看:守护线程(和守护进程用法相同都需要objt.daemon=True)
from threading import Thread import time def task1(): print('123') time.sleep(10) print('123done') def task2(): print('456') time.sleep(1) print('456done') if __name__ == '__main__': t1=Thread(target=task1) t2=Thread(target=task2) t1.daemon=True t1.start() t2.start() print('主')
五、死锁与递归锁
1、死锁现象
from threading import Thread,Lock,RLock import time mutexA=Lock() mutexB=Lock() class Mythread(Thread): def run(self): self.f1() self.f2() def f1(self): mutexA.acquire() print('\033[45m%s 抢到A锁\033[0m' %self.name) mutexB.acquire() print('\033[44m%s 抢到B锁\033[0m' %self.name) mutexB.release() mutexA.release() def f2(self): mutexB.acquire() print('\033[44m%s 抢到B锁\033[0m' %self.name) time.sleep(1) mutexA.acquire() print('\033[45m%s 抢到A锁\033[0m' %self.name) mutexA.release() mutexB.release() if __name__ == '__main__': for i in range(20): t=Mythread() t.start()
当第一个线程在f2抢到mutexB等待mutexA释放
第二个线程 在f1抢到mutexA等待mutexB释放就会变成死所。
2、递归锁
from threading import Thread,Lock,RLock import time mutex=RLock() class Mythread(Thread): def run(self): self.f1() self.f2() def f1(self): mutex.acquire() print('\033[45m%s 抢到A锁\033[0m' %self.name) mutex.acquire() print('\033[44m%s 抢到B锁\033[0m' %self.name) mutex.release() mutex.release() def f2(self): mutex.acquire() print('\033[44m%s 抢到B锁\033[0m' %self.name) time.sleep(1) mutex.acquire() print('\033[45m%s 抢到A锁\033[0m' %self.name) mutex.release() mutex.release() if __name__ == '__main__': for i in range(20): t=Mythread() t.start()
RLock是一个递归锁可以无限次mutex.acquire(),
工作原理是:有一个计数器acquire就加1,release一次就减一。其他线程要抢的时候计数器必须变成0才能抢。
六、信号量Semaphore
同进程的一样
Semaphore管理一个内置的计数器,
每当调用acquire()时内置计数器-1;
调用release() 时内置计数器+1;
计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()。
实例:(同时只有5个线程可以获得semaphore,即可以限制最大连接数为5):
from threading import Thread,current_thread,Semaphore import time,random sm=Semaphore(5) #sm一把锁 def work(): sm.acquire() #最多5个线程可以执行这段代码 print('%s 上厕所' %current_thread().getName()) time.sleep(random.randint(1,3)) sm.release() if __name__ == '__main__': for i in range(20): t=Thread(target=work) t.start()
【注意】信号量与进程池类似:但是进程池是只能控制进程的数量,不会多余这个数据,
但是信号量是控制运行锁住执行代码的线程的数量(而不是控制线程的数量
七、事件Event
python线程的事件用于主线程控制其他线程的执行,事件主要提供了三个方法wait、clear、set
事件处理的机制:全局定义了一个“Flag”,如果“Flag”值为 False,那么当程序执行 event.wait 方法时就会阻塞,如果“Flag”值为True,那么event.wait 方法时便不再阻塞。
clear:将“Flag”设置为False
set:将“Flag”设置为True
用 threading.Event 实现线程间通信
使用threading.Event可以使一个线程等待其他线程的通知,我们把这个Event传递到线程对象中,Event默认内置了一个标志,初始值为False。
一旦该线程通过wait()方法进入等待状态,直到另一个线程调用该Event的set()方法将内置标志设置为True时,该Event会通知所有等待状态的线程恢复运行。
from threading import Thread,current_thread,Event import time event=Event() #设置一个事件类 def conn_mysql(): event.wait() print('%s 链接ok' % current_thread().getName()) def check_mysql(): print('%s 正在检查mysql状态' %current_thread().getName()) time.sleep(6) event.set()if __name__ == '__main__': t1=Thread(target=conn_mysql) t2=Thread(target=conn_mysql) check=Thread(target=check_mysql) t1.start() t2.start() check.start()
#当执行完event.set()后,event.wait()后面的代码才能执行。
from threading import Thread,current_thread,Event import time event=Event() #设置一个事件类 def conn_mysql(): count=1 while not event.is_set(): if count > 3: raise ConnectionError('链接失败') print('%s 等待第%s次链接mysql' %(current_thread().getName(),count)) event.wait(0.5) count+=1 print('%s 链接ok' % current_thread().getName()) def check_mysql(): print('%s 正在检查mysql状态' %current_thread().getName()) time.sleep(1) event.set() if __name__ == '__main__': t1=Thread(target=conn_mysql) t2=Thread(target=conn_mysql) check=Thread(target=check_mysql) t1.start() t2.start() check.start()
# event.wait(2)等待2秒后无论其他线程是否执行event.set()都往后执行。
八、定时器和线程queue
from threading import Timer def hello(): print("hello, world") t = Timer(1, hello) t.start() # after 1 seconds, "hello, world" will be printed
import queue # q=queue.Queue(3) q.put(1) q.put(2) q.put(3) print(q.get()) print(q.get()) print(q.get()) q=queue.LifoQueue(3) q.put(1) q.put(2) q.put(3) print(q.get()) print(q.get()) print(q.get()) q=queue.PriorityQueue(3) q.put((10,'data1')) q.put((11,'data11')) q.put((9,'data9')) print(q.get()) print(q.get()) print(q.get()) # (9, 'data9') # (10, 'data1') # (11, 'data11')
#数组越小 优先级越高
九、进程池池与线程池concurrent.futures
使用concurrent.futures模块来启动多进程和多线程,比threading和press更加简单:
1、起多进程
# -*- coding: utf-8 -*- __author__ = 'ShengLeQi' #进程池 from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor import os,time,random def work(n): print('%s is running' %os.getpid()) time.sleep(random.randint(1,4)) return n**2 if __name__=='__main__': p=ProcessPoolExecutor() #默认开启个数是:cpu的个数 res_l=[] for i in range(20): res=p.submit(work,i) res_l.append(res) p.shutdown() for obj in res_l: print(obj.result())
2、起多线程
#线程池 from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor from threading import current_thread import os,time,random def work(n): print('%s is running' %current_thread().getName()) time.sleep(random.randint(1,4)) return n**2 if __name__=='__main__': p=ThreadPoolExecutor(4) #线程需要指定个数,默认开启个数是:cpu*5 res_l=[] for i in range(20): res=p.submit(work,i) res_l.append(res) p.shutdown() for obj in res_l: print(obj.result())
3、模仿爬虫(多线程)
import requests,os,time from multiprocessing import Pool from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor def get_page(url): print('<%s> get :%s' %(os.getpid(),url)) request=requests.get(url) if request.status_code==200: return {'url':url,'text':request.text} def parse_page(obj): dic=obj.result() print('<%s> parse: %s' %(os.getpid(),dic['url'])) print('url:%s size :%s' %(dic['url'],len(dic['text']))) if __name__=='__main__': # p = Pool(4) # p=ProcessPoolExecutor(2) p=ProcessPoolExecutor(2) urls=[ 'https://www.baidu.com', 'https://www.python.org', 'http://www.sina.com.cn', 'https://www.openstack.org', 'https://about.gitlab.com' ] for url in urls: p.submit(get_page,url).add_done_callback(parse_page) p.shutdown() print('===>主进程')
4、map的使用(如果不需要回调函数,可以直接使用map来实现)
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor from threading import current_thread import os,time,random def work(n): print('%s is running' %current_thread().getName()) time.sleep(random.randint(1,4)) return n**2 if __name__=='__main__': p=ThreadPoolExecutor(4) #线程需要指定个数,默认开启个数是:cpu*5 # res_l=[] # for i in range(20): # res=p.submit(work,i) # res_l.append(res) # p.shutdown() # for obj in res_l: # print(obj.result()) obj=map(work,range(10)) p.shutdown() print(list(obj))

浙公网安备 33010602011771号