day33 python学习 多线程
线程的概念
进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合),而线程才是cpu上的执行单位。
三 线程与进程的区别
1
1.线程的创建开销小(无需申请内存空间或者资源),创建线程的速度快,(但是不能无限开)
2.同一个进程下的多个线程,共享该进程地址空间的资源(数据等内容)
3.改变线程,会影响其他线程(在同一个进程的内存空间内的线程) 但是改变主进程,不会影响子进程
- Threads share the address space of the process that created it; processes have their own address space.
- 进程分享创建他的留给他们的地址空间,而进程们却又自己的地址空间
- Threads have direct access to the data segment of its process; processes have their own copy of the data segment of the parent process.
- 线程直接访问进程的数据段;进程拥有父进程的数据段的自身副本。
- Threads can directly communicate with other threads of its process; processes must use interprocess communication to communicate with sibling processes.
- New threads are easily created; new processes require duplication of the parent process.
- Threads can exercise considerable control over threads of the same process; processes can only exercise control over child processes.
- Changes to the main thread (cancellation, priority change, etc.) may affect the behavior of the other threads of the process; changes to the parent process does not affect child processes.
四 为何要用多线程
多线程指的是,在一个进程中开启多个线程,简单的讲:如果多个任务共用一块地址空间,那么必须在一个进程内开启多个线程。详细的讲分为4点:
1. 多线程共享一个进程的地址空间
2. 线程比进程更轻量级,线程比进程更容易创建可撤销,在许多操作系统中,创建一个线程比创建一个进程要快10-100倍,在有大量线程需要动态和快速修改时,这一特性很有用
3. 若多个线程都是cpu密集型的,那么并不能获得性能上的增强,但是如果存在大量的计算和大量的I/O处理,拥有多个线程允许这些活动彼此重叠运行,从而会加快程序执行的速度。
4. 在多cpu系统中,为了最大限度的利用多核,可以开启多个线程,比开进程开销要小的多。(这一条并不适用于python)
开启线程的两种方式
与开启进程的方式类似
from threading import Thread def piao(name,n): print('%s is piaoing %s and%s'%(name,n,os.getpid())) if __name__ == '__main__': t=Thread(target=piao,args=('alex',11,)) #这里的写法要注意, t.start() print('zhu')
from threading import Thread import os class Piao(Thread): def __init__(self,name): super(Piao, self).__init__() self.name=name def run(self): # 方法名一定要写成RUN 的形式否则不行 print('%s is piaoing'%self.name) if __name__ == '__main__': t=Piao('alex') t.start() print('主')
六 守护线程
无论是进程还是线程,都遵循:守护xxx会等待主xxx运行完毕后被销毁
需要强调的是:运行完毕并非终止运行
#1.对主进程来说,运行完毕指的是主进程代码运行完毕 #2.对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕
详细解释:
#1 主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收),然后主进程会一直等非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束, #2 主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,
而进程必须保证非守护线程都运行完毕后才能结束。
线程池:与进程池概念类似(引入进程池模块就可以了,无须再引入Thread模块)
池的目的:当有很大的工作量需要去完成的时候,需要限制每次进入线程(或者进程)的数量。
from concurrent.futures import ThreadPoolExecutor #引入线程池模块 def piao(name): print('%s is piao ing '%name) if __name__ == '__main__': t=ThreadPoolExecutor(4) # objs=[] for i in range(10): obj=t.submit(piao,'alex') objs.append(obj) t.shutdown(wait=True) for obj in objs: print(obj.result()) print('zhu')
进程池
- 很明显需要并发执行的任务通常要远大于核数
- 一个操作系统不可能无限开启进程,通常有几个核就开几个进程
- 进程开启过多,效率反而会下降(开启进程是需要占用系统资源的,而且开启多余核数目的进程也无法做到并行)
ps:对于远程过程调用的高级应用程序而言,应该使用进程池,Pool可以提供指定数量的进程,供用户调用,当有新的请求提交到pool中时,
如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到规定最大值,那么该请求就会等待,
直到池中有进程结束,就重用进程池中的进程。
from concurrent.futures import ProcessPoolExecutor import os,time n=100 def piao(name): print('%s is piaoing'%name,os.getpid()) time.sleep(1) global n return n**2 if __name__ == '__main__': p=ProcessPoolExecutor(4) objs=[] for i in range(10): obj=p.submit(piao,i) objs.append(obj) p.shutdown(wait=True) for obj in objs: print(obj.result()) print('zhu',os.getpid())
打印结果如下:: 可以看到四个进程的进程ID 都是固定的,这就是进程池,当当前进程完成后进入下一个进程,此时还是用的这些进程ID
证明进程池是开
0 is piaoing 1348
1 is piaoing 14076
2 is piaoing 13816
3 is piaoing 13756
4 is piaoing 1348
5 is piaoing 14076
6 is piaoing 13816
7 is piaoing 13756
8 is piaoing 1348
9 is piaoing 14076
t.submit(func1,obj).add_done_callback(函数名) 回调函数
from concurrent.futures import ThreadPoolExecutor import time from threading import current_thread import requests #模拟浏览器,走HTTP协议发送请求,模块下载网页 def func1(url): print('%s GET %s'%(current_thread().getName(),url)) #得到当前线程的名称 response=requests.get(url) #得到网页内容 time.sleep(3) if response.status_code==200: #status_code response的返回的状态码,当为200时才表示下载成功 return{'url':url,'text':response.text}#response.text 得到下载的内容 def res(obj): ret=obj.result() print('[%s]<%s> %s'%(current_thread().getName(),ret['url'],len(ret['text']))) if __name__ == '__main__': t=ThreadPoolExecutor(2) urllis=[ 'https://www.jd.com', 'https://www.tmall.com', 'https://www.baidu.com', 'https://www.douban.com' ] for url in urllis: t.submit(func1,url).add_done_callback(res) #回调函数,将函数func1执行的结果返回给res, #在res中用 .result()的方式来取出返回值 t.shutdown(wait=True) print('zhu') import struct a=struct.pack('i',11111) print(a) for i in a: print(bin(i)) print(bin(11111))