定义:
进程:资源的集合,一个程序就是一个进程。
线程:一个程序最小的运行单位。
import threading #引入线程模块
importtimedefrun():
time.sleep(5)print('over')
start_time=time.time()
run()
run()
run()
run()
end_time=time.time()print('run_time', end_time-start_time)#结果:run_time=20.38954234234
上面这个例子是单线程执行的,运行时间是20多秒,如果使用多线程,则会并行执行,执行结果应该是5秒左右。
主线程等待子线程
方法一:想要让主线程等待添加的线程,需要先把创建的线程统一放到list里面,循环执行完,使用.join()方法,如下:
importthreadingimporttimedefrun():
time.sleep(5)print('over')
start_time=time.time()
thread_list=[]for i in range(5):
t=threading.Thread(target=run) #实例化一个线程,target代表要指定执行什么,例如:target=run就是执行run()这个函数,指定函数名,不要加()
#函数里面如果有参数,可以加上args,如:t=threading.Thread(target=run,args[1,2,3,]) ,有几个参数,就在[]里面写几个参数
t.start() #启动这个线程
thread_list.append(t) #把线程放到list里面for thread inthread_list:
thread.join()#主线程等待子线程
end_time=time.time()print('run_time=', end_time-start_time)
方法二:每一次循环都会判断下剩余的线程是不是只剩下1个,不是一个的话,继续循环,直到剩下的线程数为1时,继续往下执行,如下:
importthreadingimporttime
threads=[]
start_time2=time.time()definsert_db():
time.sleep(3)print('insert_db over')for i in range(3):
t= threading.Thread(target=insert_db)
t.start()while threading.activeCount()!=1:#每一次循环都会判断下剩余的线程是不是只剩下1个,不是一个的话,继续循环,直到剩下的线程数为1时,继续往下执行
passend_time2=time.time()print('多线程执行的时间',end_time2 -start_time2)print('锁门...')
线程 单个启动 & 同时启动
importthreading'''单个启动'''
for i in range(10):
t= threading.Thread(target=func,args=[1])
t.start()#start()在这里是一个一个线程拿过来,单个启动
'''同时启动启动'''ts=[]for i in range(10):
t= threading.Thread(target=func,args=[1])
ts.append(t)#先10个线程放到一个list里面
for t ints:
t.start()#循环同时启动
练习分析:
importthreadingimporttimedefinsert_db():
time.sleep(3)print('insert_db over')
start_time=time.time()for i in range(3):
t= threading.Thread(target=insert_db)
t.start()
end_time=time.time()print('多线程执行的时间',end_time -start_time)
结果:
多线程执行的时间0.0010001659393310547insert_db over
insert_db over
insert_db over
我们看到的结果是中运行时间是0.0010001659393310547,还不到一秒钟,而我们代码所设置的是最少也会需要3秒钟,为什么时间会是这么短呢?
——因为所得到的时间只是主线程执行完它的工作时间,并不包括子线程执行的额时间。
压测练习:
importthreadingimportrequestsdefrequest():whileTrue:
r= requests.get('http://api.nnzhp.cn/api/user/stu_info?stu_name=%E7%8E%8B%E5%B0%8F%E6%9C%88')print(r.json())for i in range(10):
t= threading.Thread(target=request)
t.start()
运行后会一直死循环,一直压测,可以加入压测的时间
单线程下载网页:
importrequests,time,threadingfrom hashlib importmd5
result_list={}defdown_load_pic(url):
req=requests.get(url)
m= md5(url.encode()) #把url转换为二进制md5下
file_name = m.hexdigest()+'.png' #拼接文件名
with open(file_name ,'wb') as fw:
fw.write(req.content)#return file_name
result_list[file_name] =threading.current_thread()
url_list= ['http://www.nnzhp.cn/wp-content/uploads/2019/10/f410afea8b23fa401505a1449a41a133.png','http://www.nnzhp.cn/wp-content/uploads/2019/11/481b5135e75c764b32b224c5650a8df5.png','http://www.nnzhp.cn/wp-content/uploads/2019/11/b23755cdea210cfec903333c5cce6895.png','http://www.nnzhp.cn/wp-content/uploads/2019/11/542824dde1dbd29ec61ad5ea867ef245.png']
start_time=time.time()for url inurl_list:
down_load_pic(url)
end_time=time.time()print(end_time -start_time)#结果:4.439253807067871
多线程下载网页:
importthreadingimportrequestsimporthashlibimporttimedefdown_load(url):
name= hashlib.md5(url.encode()).hexdigest() #把url使用md5转化为密文
r =requests.get(url)
with open('%s.jpg'%name,'wb') as fw:
fw.write(r.content)
l=['http://www.nnzhp.cn/wp-content/themes/QQ/images/logo.jpg','http://www.nnzhp.cn/wp-content/uploads/2016/12/2016aj5kn45fjk5-150x150.jpg','http://www.nnzhp.cn/wp-content/themes/QQ/images/thumbnail.png']for i inl:
t= threading.Thread(target=down_load,args=[i])#参数只有一个时,要使用()的方式来写,需要多加一个逗号,如:args=(i,)
#args是存参数的,如果里面只有一个参数的话,一定要在这个参数后面加一个逗号,因为是保存在元组里,如果不加逗号,它会默认为是字符串 应该写成:args=(url,)
t.start()while threading.activeCount()!=1:pass
print('down load over...')
一个进程里面至少有一个线程,这个线程就是主线程。
主线程只是调度用的,它把子线程招来之后就完事了,因此如果要统计运行时间,必须要让主线程等待所有的子线程都执行完后再记录结束时间。
case_result =[]defrun_case(case_name):print('run case..',case_name)
case_result.append( {case_name:'success'})#多线程运行函数时,函数的返回值是拿不到的,所以定义一个list#把函数运行的结果都存进去就ok了
查看当前有多少个线程:threading.activeCount()
import threading #引入线程模块
import time
def run():
time.sleep(1)for i in range(10):
t= threading.Thread(target=run)
t.start()
print('当前有%s个线程'%threading.activeCount()) #当前有几个线程
结果:当前有11个线程
为什么会是11个线程而不是10个线程呢?我们明明是循环启动了10个线程,结果怎么就是11个了呢?
因为本身有一个主线程在运行,好比是我们自己在干活,我们又找了10个人来一起干活,加上我们自己一共是11个人在干活。所有的程序默认就会有一个线程。
2、守护线程
一旦主线程死掉,那么守护线程不管有没执行完事,全部结束,相当于你是一个国王(主线程),你有很多仆人(守护线程),仆人都是为你服务的,一旦你死了,那么你的仆人都需要给你陪葬。
importthreadingimporttimedeftalk(name):print('正在和%s聊天'%name)
time.sleep(200)print("qq主窗口")
t= threading.Thread(target=talk,args=['刘一'])
t.setDaemon(True)#设置线程为守护线程
t.start()
time.sleep(5)print('结束。。。')
结果:
qq主窗口
正在和刘一聊天
结束。。。
如果是没有设置为守护线程,本身程序执行需要有200秒才可以结束,设置了守护线程后,主线程5秒到了就会结束,主线程结束了,剩下的正在执行的线程已经是守护线程了,不会继续200秒结束,会立即跟随主线程结束。
3、线程锁
多个线程同时在操作同一个数据的时候,会有问题,就要把这个数据加个锁,然后同一时间只能有一个线程操作这个数据了【多个人或是多线程在同时操作同一个数据时,可能会有问题,可以加下锁】
忘记了解锁或是代码运行时没有处理异常而没有走到解锁那里,都会成为线程死锁。
举例理解:比如说我们家里的卫生间,男女共用的,如果你进去时,没有锁门,有可能会有其他的开门,所以你需要进去时把门锁一下,不用了,再把锁打开下。
importthreadingfrom threading importLock
num=0
lock= Lock()#申请一把锁
defrun():globalnum
lock.acquire(timeout = 3)#加锁,timeout是指定下时间,可以不写,如果指定了时间,超过后申请的锁就会失效
num+=1lock.release()#解锁
lis=[]for i in range(5):
t= threading.Thread(target=run)
t.start()
lis.append(t)for t inlis:
t.join()print('over',num)
#多个线程操作同一个数据的时候,就得加锁
importthreading
num=0
lock= threading.Lock() #申请一把锁,实例化一把锁
defadd():globalnum#lock.acquire()#加锁
#num+=1
#lock.release()#解锁 #不解锁,就会产生线程死锁,会一直在等待
with lock:#简写,用with也会帮你加锁,解锁
num+=1
for i in range(20):
t= threading.Thread(target=add,)
t.start()while threading.activeCount() !=1:pass
查看当前哪个线程在执行:threading.current_thread()
importthreading
count=0
lock=threading.Lock()deftest():globalcountprint(threading.current_thread())
lock.acquire()#加锁
count+=1lock.release()#解锁
#线程死锁
for i in range(3):
t= threading.Thread(target=test)
t.start()
结果:
下面来个简单的爬虫,看下多线程的效果:
importthreadingimportrequests, 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",
}defrun(name, url):
res=requests.get(url)
with open(name+ '.html', 'w', encoding=res.encoding) as fw:
fw.write(res.text)
start_time=time.time()
lis=[]for url inurls:
t= threading.Thread(target=run, args=(url, urls[url]))
t.start()
lis.append(t)for t inlis:
t.join()
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))
4、多进程,上面说了Python里面的多线程,是不能利用多核CPU的,如果想利用多核CPU的话,就得使用多进程,python中多进程使用multiprocessing模块。
一个电脑有几核的CPU,就只能同时运行几个任务
——比如说我们的电脑是4核的CPU,只可以同时运行4个进程,但我们在实际使用中,如果是4核的CPU,运行的并不止是4个程序,这是因为CPU上下文切换的,这个程序运行完了,
会立即切换到另外一个运行,我们感觉不到。
importmultiprocessing,time
lock= multiprocessing.Lock() #申请一把锁
a = 1
defdown_load():
time.sleep(3)globala
with lock:#使用with这个方法会自动的去判断使用完了自动的关掉锁释放【with是自动管理上下文的,操作文件时也可以使用,会自动识别关闭】
a+=1
print("运行完了")if __name__ == '__main__':for i in range(5):
p= multiprocessing.Process(target=down_load)#p=multiprocessing.Process(target=down_load,args =[1,2]) #有参数了可以加上args
p.start()while len(multiprocessing.active_children())!=0:#等待子进程结束
pass
print(multiprocessing.current_process())print('end')
5、进程池
还可以使用进程池来快速启动几个进程,使用进程池的好处的就是他会自动管理进程数,咱们只需要给他设置一个最大的数就ok了。有新的请求提交到Pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到指定的最大值,那么该请求就会等待,直到池中有进程结束,才会用之前的进程来执行新的任务。【数据大时使用】
from multiprocessing importPoolimportosdefworker(msg):print("%s开始执行,进程号为%d" %(msg,os.getpid()))if __name__ == '__main__':
po= Pool(3) #定义一个进程池,最大进程数3
for i in range(0, 10):#Pool().apply_async(要调用的目标,(传递给目标的参数元祖,))
#每次循环将会用空闲出来的子进程去调用目标
po.apply_async(func=worker,args=(i,))#第一个func参数指定运行的函数,第二个args是参数,没有参数可以不写
print("----start----")
po.close()#关闭进程池,关闭后po不再接收新的请求
po.join() #等待po中所有子进程执行完成,必须放在close语句之后
print("-----end-----")
总结:
线程是用来干活的,只有进程的话是没办法运行的,进程里其实是线程在具体干活的。
线程和线程之间是互相独立的。
线程是在进程里面
默认,一个进程里面只有一个进程
进程相当于是一个工厂,线程相当于是工厂里面具体干活的一个人
主线程,也就是程序一开始运行的时候,最初的那个线程
子线程,通过thread类实例化的线程,都是子线程
主线程等待子线程,执行结束后,主线程再去做别的操作
主线程执行完它自己工作的时间,并不包括子线程执行的时间
Python里面的多线程利用不了多核的cpu,因为如果是在多个CPU上运行,运行的结果会不太一样,所以加入一个全局解释器锁(GLI),保证线程都在同一个cpu运行
多进程可以利用多核CPU
CPU密集型任务,用多进程 ->消耗的CPU比较多
IO(磁盘IO、网络IO)密集型任务,用多线程 ->消耗IO比较多【IO是上传、下载的意思】
多线程,线程之间的的数据是共享的
多进程,每个进程之间的数据是独立的,正是因为每个进程都是独立的,所以多进程里面加锁是没任何意义的。
扩展:
协程,只有有个线程在运行,每遇到消耗IO的地方就会立马切换,在切换到另一个,等着这个IO结束了,再去拿到数据,性能比较好;如:nginx就是使用的协程。
任何付出都是值得的,会越来越好