python:多进程、多线程


线程&进程
对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程,打开一个记事本就启动了一个记事本进程,打开两个记事本就启动了两个记事本进程,打开一个Word
就启动了一个Word进程。进程是很多资源的集合。
有些进程还不止同时干一件事,比如Word,它可以同时进行打字、拼写检查、打印等事情。在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread
)。
由于每个进程至少要干一件事,所以,一个进程至少有一个线程。当然,像Word这种复杂的进程可以有多个线程,多个线程可以同时执行,多线程的执行方式和多进程是一样的,也是由操作系统在多个线程之间
快速切换,让每个线程都短暂地交替运行,看起来就像同时执行一样。当然,真正地同时执行多线程需要多核CPU才可能实现。线程是最小的执行单元,而进程由至少一个线程组成。
我们在做事情的时候,一个人做是比较慢的,如果多个人一起来做的话,就比较快了,程序也是一样的,我们想运行的速度快一点的话,就得使用多进程,或者多线程,在python里面,多线程被很多人诟病,为
什么呢,因为Python的解释器使用了GIL的一个叫全局解释器锁,它不能利用多核CPU,只能运行在一个cpu上面,但是你在运行程序的时候,看起来好像还是在一起运行的,是因为操作系统轮流让各个任务交替
执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务3,执行0.01秒……这样反复执行下去。表面上看,每个任务都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像
所有任务都在同时执行一样。这个叫做上下文切换。
进程:
一个进程就是一个软件、一个程序
python的多进程,可以利用电脑的多核cpu
线程:
是进程里面最小的执行单元,一个进程里可以有多个线程,最少有一个线程
没有真正意义上的并发,电脑cpu是几核的,就只能最多同时处理几个任务(线程)
python里面的多线程,是利用不了多核cpu的,只能利用一个核心的cpu;所以有些情况下,用多线程的时候比单线程速度还慢

一、什么时候用多进程、多线程
#1、多线程适用于io密集型任务(读写文件、下载东西、请求接口等)
磁盘io: input output
网络io:
#2、多进程适用于cpu密集型任务(排序、科学计算等)

二、相同的代码,为何有时候多线程会比单线程慢,有时又会比单线程快?
这主要跟运行的代码有关:
1、CPU密集型代码
(各种循环处理、计数等等 ),在这种情况下,由于计算工作多, ticks计数很快就会达到
100阈值,然后触发 GIL的释放与再竞争 (多个线程来回切换当然是需要消耗资源的),
所以 python下的多线程遇到 CPU密集型代码时,单线程比多线程效率高。
2、IO密集型代码 (文件处理、网络爬虫等 ),多线程能够有效提升效率 (单线程下有
IO操作会进行 IO等待,造成不必要的时间浪费,而开启多线程能在线程 A等待时,自动切换
到线程 B,可以不浪费 CPU的资源,从而能提升程序执行效率 )。
进行 IO密集型的时候可以进行分时切换 所以这个时候多线程快过单线程。(
3、如果 python想充分利用多核 CPU,可以采用多进程, 每个进程有各自独立的 GIL,
互不干扰,这样就可以真正意义上的并行执行,所以在 python中,多进程的执行效率优于多线
程 (仅仅针对多核 CPU而言 )。
所以在多核 CPU下,想做并行提升效率,比较通用的方法是使用多进程,能够有效提高执行效率。

多线程:
多线程要用到threading这个模块
eg1:
import threading,time
def run(count):
time.sleep(2)
print(count)
start=time.time()
threads=[]
for i in range(5):
t=threading.Thread(target=run,args=(i,))
#创建一个线程,指定它运行哪个函数;args=(i,)是run函数的入参,一个参数要加逗号,没有参数就不写args
#如果不加线程,这段代码运行需要10秒,加了线程时间就减少很多
t.start()
threads.append(t)
#把启动的每一个线程添加到这个线程组里面

for th in threads:
th.join() #th代表的是每个子线程,join方法是等待子线程结束的意思,子线程结束了才继续往下走;
#如果没有join的话,就是主线程和子线程同时执行,子线程因为要sleep两秒,所以主线程先走完,然后子线程走完
end=time.time()
print('总共运行了',end-start)
在运行python文件时,如果你不启动多线程,那最少有一个线程在运行
线程之间是相互独立的,都是同时执行,互相没有关系
程序开始运行的时候的线程是主线程,主线程先走,并不是等其他线程走完它再走
通过多线程去调用一个函数的时候,是没有办法获取到这个函数的返回值的;如果多线程想获取返回结果,可以把返回值放添加到列表里
上面的例子,线程有6个

例一:(启动线程,串行和并行)
import threading,time
def run():
time.sleep(5)
print('哈哈哈')

for i in range(5): #这种叫串行,所需时间长
run() #循环调用run函数,需要25秒

for j in range(5): #这种叫并行,所需时间短
t=threading.Thread(target=run) #启动一个线程;函数有参数的话,(target=run,args=(i,)),只有一个参数的话,需要加逗号
t.start() #循环启动5个run线程,只需要5秒
#多线程运行函数的时候,无法获得函数的返回值。所以函数有返回值的情况,就定义一个全局变量list,在函数里面把返回值插入到list

例二:(主线程、子线程;线程之间相互独立)
import threading,time
def run():
time.sleep(5)
print('哈哈哈')
start=time.time()
for i in range(5):
t = threading.Thread(target=run)
t.start()
end=time.time()
print(end-start) #试图获取run函数运行的时间
#结果是0.000576019287109375,不是5个子线程并行的时间
#原因:1、线程和线程之间是互相独立的,互不影响,同时运行
#2、代码从上到下运行就是主线程,for循环里面的是5个子线程,计算时间的代码是在主线程里面,主线程先运行完,所以时间是0.0005
#3、主线程帮子线程启动

例三:(主线程等待子线程的方法一)
import threading,time
def run():
time.sleep(5)
print('哈哈哈')
start=time.time()
for i in range(5):
t = threading.Thread(target=run)
t.start()
while threading.active_count()!=1: #当活跃的线程个数不等于1时。意思就是当活跃的线程只剩主线程时,就跳出循环往下走
pass #这里不能写!=0,写0的话,就一直死循环了
print(threading.current_thread()) #查看当前运行的是主线程还是子线程
end=time.time()
print(end-start) #结果是5.012634038925171,这次主线程是最后运行完的

例四:(主线程等待子线程的方法二)
import threading,time
def run():
time.sleep(5)
print('哈哈哈')
start=time.time()
threads=[] #存放所有的子线程
for i in range(5): #先统一启动所有的子线程
t = threading.Thread(target=run)
threads.append(t) #把子线程加入到list里
t.start()
for t in threads: #再统一的去等待子线程执行结束
t.join() #t.join()是主线程等待当前子线程运行结束的意思。
#这个循环中主线程要一一等待5个子线程运行结束,但因为上面循环中5个子线程已经统一的几乎同时启动了,
#所以,这里的等待,也是统一的等待全部子线程运行完毕
end=time.time()
print(end-start)


设置守护线程:
#子线程被设置为守护线程的话,该子线程就守护主线程,主线程结束,子线程也就立马结束
#举个例子,只要QQ关了,所有的聊天对话框也就关闭了
例五:(守护线程)
import threading,time
def run():
time.sleep(9)
print('run......')
for i in range(10): #主线程启动了10个子线程
t=threading.Thread(target=run)
t.setDaemon(True) #设置子线程为守护线程
t.start()
print('over...') #结果是'over...',子线程没有运行。
#因为主线程代码走到这里结束了,被设置为守护线程的子线程没运行完,就也跟着结束了



线程锁
线程锁就是,很多线程一起在操作一个数据的时候,可能会有问题,就要把这个数据加个锁,同一时间只能有一个线程操作这个数据。
#多个线程在操作同一个数据的时候,为了防止数据错误,最好加上锁
例一:加锁第一种方式
import threading,time
from threading import Lock
num=0
lock=Lock() #实例化一把锁
def run():
global num
lock.acquire() #手动加锁,python3里面虽然会自动加锁,但也最好加上锁
num+=1
lock.release() #手动解锁,如果不解锁的话,其他的线程就不能操作这个数据了,就会导致死锁。
for i in range(100):
t=threading.Thread(target=run)
t.start()
print(num)

#加锁第二种方式
def run():
global num
with lock: #自动会加锁解锁,不用手动解锁了
num+=1


多线程下载东西一:
import threading
import requests, time

urls = {
"baidu": 'http://www.baidu.com',
"blog": 'http://www.nnzhp.cn',
"besttest": 'http://www.besttest.cn',
"taobao": "http://www.taobao.com",
"jd": "http://www.jd.com",
}

def run(name, url):
res = requests.get(url)
with open(name + '.html', 'w', encoding=res.encoding) as fw:
fw.write(res.text)
#把网站上的html内容下载到文件里
start_time = time.time()
lis = []
for url in urls:
t = threading.Thread(target=run, args=(url, urls[url]))
t.start()
lis.append(t)
for t in lis:
t.join() #下载html内容的子线程结束之后再走这里
end_time = time.time()
print('run time is %s' % (end_time - start_time))

# 下面是单线程的执行时间
# start_time = time.time()
# for url in urls:
# run(url,urls[url])
# end_time = time.time()
# print('run time is %s'%(end_time-start_time))

多线程下载东西二:
import threading
def downloads(qq): #定义一个下载东西的函数,参数是qq
pass #下载代码省略
list1=['4244','454354','453452'] #定义个list,里面存放所有的qq号
for i in list1:
t=threading.Thread(target=downloads,args=(i,)) #i就是参数qq
t.start()
while threading.active_count()!=1:
pass
print('下载完成!')

#线程池
#线程池:多线程下载东西
import pymongo,requests
import threadpool #线程池
client=pymongo.MongoClient(host='118.11.2.2',port=27017)
table=client['hn']['test'] #从hn数据库里找出test这个表
all_qq=[i.get('qq') for i in table.find()] #从test表里找出所有的数据,并获取到qq号存在列表里

pool=threadpool.ThreadPool(200) #线程池的大小,根据电脑性能设置
#有了线程池,就不需要再循环设置多少线程了
url='http://q4.qlogo.cn/g?b=qq&nk=%s&s=140' #获取qq头像的链接
def down_img(qq):
res=requests.get(url%qq).content
with open('%s.jpg'%qq,'wb') as fw:
fw.write(res)
all_requests=threadpool.makeRequests(down_img,all_qq) #makeRequests会自动从all_qq参数里给线程分配数据;括号里是指定函数和参数
for a in all_requests:
pool.putRequest(a) #发请求
pool.wait() #等待所有线程运行完
print('下载完成!')
 
多进程:
python里面的多线程是利用不了多核cpu的
多进程是可以利用多核cpu的,python中多进程使用multiprocessing模块
from multiprocessing import Process,Pool
import pymongo,requests
client=pymongo.MongoClient(host='118.11.2.2',port=27017)
table=client['hn']['test'] #从hn数据库里找出test这个表
all_qq=[i.get('qq') for i in table.find()] #从test表里找出所有的数据,并获取到qq号存在列表里

url='http://q4.qlogo.cn/g?b=qq&nk=%s&s=140' #获取qq头像的链接
def down_img(qq):
res=requests.get(url%qq).content
with open('%s.jpg'%qq,'wb') as fw:
fw.write(res)
#第一种启动进程方法
if __name__=='__main__': #多进程必须要加这个
for qq in all_qq:
p=Process(target=down_img,args=(qq,))
p.start()
print(active_children())

#进程池启动方法
if __name__=='__main__': #多进程必须要加这个
pool=Pool(5) #指定进程池大小
list(pool.map(down_img,all_qq)) #运行,list里面是个生成器,需要转化为list才能用

#多线程和多进程语法一样
#多进程适用于cpu密集型任务,即操作cpu比较多的
#多线程适用于io密集型任务,即操作input/output比较多的,比如操作磁盘
posted @ 2017-10-22 01:59  hesperid  阅读(227)  评论(0编辑  收藏  举报