- 子类继承父类的初始化方法
class student1(student): #student为父类,父类有name,age,stno这些属性 def __init__(self,name,age,stno,addr): #在父类的基础上加了一个addr属性 student.__init(self,name,age,stno) #显示调用父类的初始化方法 self.addr=addr #不在父类的属性自行初始化
class myThread(threading.Thread): #继承父类Thread def __init__(self,url): super().__init__() #通过super()关键字显示调用父类 self.url=url
创建线程的两种方法
- 通过Thread类构造器来创建线程
def get_web(url): #定义创建线程之后要执行的方法,即是线程的执行体 headers = { 'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36' } req = urllib.request.Request(url,headers=headers) resp = urllib.request.urlopen(req) print(resp.read().decode()[:50]) if __name__=='__main__': t=threading.Thread(target=get_web,args=(url,)) #使用Thread类的构造器来创建线程,在线程参数中规定线程的执行方法和方法的参数 t.start() #运行线程 t.join() #阻塞线程,执行主程序
- 通过继承Thread类创建线程
class myThread(threading.Thread) #定义类来继承Thread,创建新线程 def __init__(self,url): #定义新类的初始化方法 super().__init__() #显示调用父类的初始化方法 self.url=url def run(self) #定义父类中的run方法,表示线程的执行过程 headers = { 'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36' } req = urllib.request.Request(self.url,headers=headers) resp = urllib.request.urlopen(req) print(resp.read().decode()[:50]) if __name__=='__main__': t=myThread(url) #初始化新定义的线程,传递新线程所需的各种参数 t.start() #运行线程 t.join() #阻塞主程序,执行线程
全局解释器锁GIL。当前线程执行I/O操作时,会释放GIL。当前线程执行超过100字节码,也会释放GIL。
import threading n = 0 lock = threading.Lock() def add(): for i in range(1000000): with lock: global n n += 1 def minus(): for i in range(1000000): with lock: global n n -= 1 if __name__=='__main__': t1 = threading.Thread(target=add) t2 = threading.Thread(target=minus) t1.start() t2.start() t1.join() t2.join() print(n) #最后执行结果还是0,加入了锁
使用Queue,消息队列来实现线程间的通信
import threading import time from queue import Queue def produce(q): kind=('猪肉','白菜','豆沙') for i in range(3): print(threading.current_thread().name,'包子生产者开始生产包子') time.sleep(1) q.put(kind[i%3]) #生产者生产后,放入一个包子到消息队列 print(threading.current_thread().name,'包子生产者生产完包子') def consume(q): while True: print(threading.current_thread().name,'消费者准备吃包子') time.sleep(1) t=q.get() #get方法是一个阻塞方法,如果消息队列中没有包子,就会阻塞当前线程 print('消费者吃了{}包子'.format(t)) if __name__=='__main__': q=Queue(maxsize=1) threading.Thread(target=produce,args=(q,)).start() #启动两个生产者线程 threading.Thread(target=produce,args=(q,)).start() threading.Thread(target=consume,args=(q,)).start() #启动一个消费者线程 #执行流程 #先启动三个线程分别执行到线程中的一部分 # 包子生产者1开始生产包子 # 包子生产者2开始生产包子 # 消费者准备吃包子 #此时消息队列中没有包子,生产者1执行剩余部分后,又循环回到头部重新生产包子 # 包子生产者1生产完包子 # 包子生产者1开始生产包子 #此时,消息队列中有包子,消费者开始消费 # 消费者吃了什么包子 #消费者消费完包子后,消息队列中没有包子,生产者2开始生产包子 # 包子生产者2生产完包子 # 包子生产者2开始生产包子 #生产者2生产包子后,消息队列中有包子,消费者吃完包子后又回到循环等待包子 # 消费者吃了什么包子 # 消费者准备吃包子 #此后,生产者1又重新开始生产包子,消费者消费包子,生产者2开始生产包子,消费者消费包子。直到两个生产者都生产完3个包子后,消息队列中没有包子,结束执行。
使用Event事件对象让线程通信。适合与集合点的测试,线程启动后,不是立刻执行,而是等待所有线程都启动后,测试系统的并发执行效率
import threading import time class myThread(threading.Thread): #自定义线程对象 def __init__(self,event): super().__init__() #继承父类初始化方法 self.event=event def run(self): #自定义run方法 print('线程{}已经启动 '.format(self.name)) self.event.wait() #启动线程后,使用wait方法阻塞所有线程,等待event指令 print('线程{}开始运行'.format(self.name)) if __name__=='__main__': event=threading.Event() #创建Event对象 threads=[] #定义线程列表 [threads.append(myThread(event) for i in range(1,11))] #创建10个线程,放入到threads列表中 event.clear() #创建线程后,使所有Event对象都处于待命状态 [t.start() for t in threads] #执行线程 event.set() #执行每个线程时,都会进入等待Event对象发送指令状态,需要set方法发送指令 [t.join() for t in threads] #使每个线程和主线程分割
在多个线程之间交替执行或一个线程在等待其他线程时,使用Condition条件对象就很合适
import threading cond=threading.Condition() #定义Condition对象 class k(threading.Thread): #定义一个要通信的对象 def __init__(self,cond,name): threading.Thread.__init__(self,name=name) self.cond=cond def run(self): #定义线程执行体 self.cond.acquire() #线程k获得锁 print(self.getName() +':一支穿云箭') self.cond.notify() #线程k发送消息后,通知线程x接收消息 self.cond.wait() #线程k通知x后,等待x的消息 print(self.getName()+':山无棱,天地合,乃敢与君决') self.cond.notify() self.cond.wait() print(self.getName()+':紫薇') self.cond.notify() self.cond.wait() print(self.getName()+':是你') self.cond.notify() self.cond.wait() print(self.getName()+':借点钱') self.cond.notify() #线程k与线程x之间最后一次通信,通知x后,释放锁 self.cond.release() #线程k释放锁 class x(threading.Thread): #定义另一个要通信的线程 def __init__(self,cond,name): #定义线程x的初始化方法 threading.Thread.__init__(self,name=name) self.cond=cond def run(self): #定义线程x的执行体 self.cond.acquire() #线程x获得锁 self.cond.wait() #线程x等待线程k发送消息 print(self.getName()+':千军万马来相见') self.cond.notify() self.cond.wait() print(self.getName()+':海可枯,石可烂,激情永不散') self.cond.notify() self.cond.wait() print(self.getName()+':尔康') self.cond.notify() self.cond.wait() print(self.getName()+':是我') self.cond.notify() self.cond.wait() print(self.getName()+':滚') self.cond.release() #线程x在发送完最后一条信息后,释放锁 if __name__ =='__main__': k = kongbeige(cond,'空白哥') x = ximige(cond,'西米哥') x.start() k.start()
执行过程
线程之间的消息隔离。定义一个全局变量,在每个子线程中都会使用到。比如网络开发中,每个用户都有一个session,使用线程之间的全局变量可以让每个用户管理自己的session对象。在线程类之中的上下文管理中,数据库连接中,都能够使用到消息隔离。
import threading local_data=threading.local() #定义一个全局变量 local_data.name='local_data' class localThread(threading.Thread): #定义子线程 def run(self): print('赋值前,主线程',threading.current_thread(),local_data.__dict__) local_data.name=self.getName() #在子线程中修改全局变量 print('赋值后,子线程',threading.current_thread(),local_data.__dict__) if __name__=='__main__': print('赋值前,主线程',threading.current_thread,local_data.__dict__) #定义主线程,和子线程比较,观察全局变量是否改变 t1 = localThread() t1.start() t1.join() t2 = localThread() t2.start() t2.join() print('赋值后,主线程',threading.current_thread,local_data.__dict__)
最后的输出结果说明进程内部的全局变量,在主线程中没有发生改变,在两个子线程中分别被修改。
为了降低创建线程造成的资源消耗,引入了线程池,在线程池中的线程执行结束后,会被回收,下一个相同的任务到来,线程会直接执行。
线程池中,主线程会获取某个线程的任务或状态,以及返回值。某个子线程执行结束后,主线程立刻知道,会回收。
from concurrent.futures import ThreadPoolExecutor import time executor=ThreadingPoolExecutor(max_workers=3) #定义一个线程池对象,最大只允许三个线程 def get_html(times): #定义线程池中子线程的执行过程 time.sleep(times) print('获取网页{}信息'.format(times)) return times task1=executor.submit(get_html,1) #把第一个线程装入线程池,只需要把子线程的方法名传入,后面跟上方法中的参数即可。 task2=executor.submit(get_html,2) task3=executor.submit(get_html,3)
在线程池中提交了线程后,可以通过提交后的函数句柄来查看线程的执行状况。
print(task1.done()) #查看线程1是否执行结束,一般在将线程提交后不会立即执行,会返回false值。 #在上面代码之前添加time.sleep(3)后,即在主线程中添加时间延迟后,线程会执行,此时查看线程状态,会返回true值 print(task2.cancel()) #查看线程2是否撤销,一般在线程没有提交到线程池,才能撤销线程 print(task1.result()) #拿到线程执行的结果,这是一个阻塞方法
在线程池中的线程执行结束后,如何查看运行结果。Python提供task.done()方法获取线程执行情况。但是这个方法在线程还没执行结束时,无法查看线程执行过程。所以有as_completed,map,wait方法可以在线程执行结束后,查看线程的状态。
from concurrent.futures import ThreadPoolExecutor,as_completed,ALL_COMPLETED urls=[1,2,3,4] #定义需要抓取的地址 def get_web(): #定义线程的执行过程 pass all_tasks=[executor.submit(get_web,url) for url in urls] #定义要执行的任务队列 for item in as_completed(all_tasks): #使用as_completed查看线程的执行情况 data=item.result() print('主线程获取任务的返回值{}'.format(data)) #使用as_completed是按照线程的执行情况来先后输出线程状态 #使用map只是按照线程的装入顺序来输出线程执行情况 for data in executor.map(get_web,urls): #定义map方法获取线程执行的状态 print('主线程获取任务的返回值{}'.format(data)) #使用wait方法即是在所有子线程结束后,再去运行主线程 wait(all_tasks,return_when=ALL_COMPLETED) #参数ALL_COMPLETED就是所有子线程执行结束后,再执行主线程
多进程的实现和多线程大体相同。
多线程具有效率高,耗费资源少,所以非常适合I/O密集型的作业,比如文件读取,爬虫程序。这些程序主要的时间消耗集中在等待时间,多线程就非常适合。多线程缺点就是稳定性差,一个线程崩溃,其余线程也会受到影响。
多进程稳定性高,进程之间互不干扰。所以非常适合计算密集型的作业。多进程缺点耗费计算机大量资源。