定义:
- 进程:资源的集合,一个程序就是一个进程。
- 线程:一个程序最小的运行单位。
import threading #引入线程模块 import time def run(): 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()方法,如下:
import threading import time def run(): 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 in thread_list: thread.join() #主线程等待子线程 end_time=time.time() print('run_time=', end_time-start_time)
方法二:每一次循环都会判断下剩余的线程是不是只剩下1个,不是一个的话,继续循环,直到剩下的线程数为1时,继续往下执行,如下:
import threading import time threads = [] start_time2 = time.time() def insert_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时,继续往下执行 pass end_time2 = time.time() print('多线程执行的时间',end_time2 - start_time2) print('锁门...')
-
线程 单个启动 & 同时启动
import threading '''单个启动''' 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 in ts: t.start() #循环同时启动
练习分析:
import threading import time def insert_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.0010001659393310547 insert_db over insert_db over insert_db over
我们看到的结果是中运行时间是0.0010001659393310547,还不到一秒钟,而我们代码所设置的是最少也会需要3秒钟,为什么时间会是这么短呢?
——因为所得到的时间只是主线程执行完它的工作时间,并不包括子线程执行的额时间。
压测练习:
import threading import requests def request(): while True: 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()
运行后会一直死循环,一直压测,可以加入压测的时间
单线程下载网页:
import requests,time,threading from hashlib import md5 result_list = {} def down_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 in url_list: down_load_pic(url) end_time = time.time() print(end_time - start_time) #结果:4.439253807067871
多线程下载网页:
import threading import requests import hashlib import time def down_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 in l: 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 = [] def run_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、守护线程
- 一旦主线程死掉,那么守护线程不管有没执行完事,全部结束,相当于你是一个国王(主线程),你有很多仆人(守护线程),仆人都是为你服务的,一旦你死了,那么你的仆人都需要给你陪葬。
import threading import time def talk(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、线程锁
-
多个线程同时在操作同一个数据的时候,会有问题,就要把这个数据加个锁,然后同一时间只能有一个线程操作这个数据了【多个人或是多线程在同时操作同一个数据时,可能会有问题,可以加下锁】
- 忘记了解锁或是代码运行时没有处理异常而没有走到解锁那里,都会成为线程死锁。
举例理解:比如说我们家里的卫生间,男女共用的,如果你进去时,没有锁门,有可能会有其他的开门,所以你需要进去时把门锁一下,不用了,再把锁打开下。
import threading from threading import Lock num = 0 lock = Lock()#申请一把锁 def run(): global num lock.acquire(timeout = 3)#加锁,timeout是指定下时间,可以不写,如果指定了时间,超过后申请的锁就会失效 num+=1 lock.release()#解锁 lis = [] for i in range(5): t = threading.Thread(target=run) t.start() lis.append(t) for t in lis: t.join() print('over',num)
#多个线程操作同一个数据的时候,就得加锁 import threading num = 0 lock = threading.Lock() #申请一把锁,实例化一把锁 def add(): global num # 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()
import threading count = 0 lock = threading.Lock() def test(): global count print(threading.current_thread()) lock.acquire()#加锁 count+=1 lock.release()#解锁 #线程死锁 for i in range(3): t = threading.Thread(target=test) t.start() 结果: <Thread(Thread-1, started 9000)> <Thread(Thread-2, started 4820)> <Thread(Thread-3, started 10224)>
下面来个简单的爬虫,看下多线程的效果:
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) 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() 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上下文切换的,这个程序运行完了,
会立即切换到另外一个运行,我们感觉不到。
import multiprocessing,time lock = multiprocessing.Lock() #申请一把锁 a = 1 def test(): pass def down_load(): for i in range(5): t = threading.Thread(target = test)#启动多进程的同时,可以在启动多个多线程 time.sleep(3) global a 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],name='brf') #有参数了可以加上args,可以定义进程名字 p.start() # print(p.pid()) 查看进程ID while len(multiprocessing.active_children())!=0:#等待子进程结束,【multiprocessing.active_children()】是查看这个进程里面存活着几个子线程 pass print(multiprocessing.current_process()) print('end')
5、进程池
还可以使用进程池来快速启动几个进程,使用进程池的好处的就是他会自动管理进程数,咱们只需要给他设置一个最大的数就ok了。有新的请求提交到Pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到指定的最大值,那么该请求就会等待,直到池中有进程结束,才会用之前的进程来执行新的任务。【数据大时使用】
from multiprocessing import Pool import os def worker(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
- 多线程,线程之间的的数据是共享的,每个线程都可以操作里面的数据
- 多进程,每个进程之间的数据是独立的,正是因为每个进程都是独立的,所以多进程里面加锁是没任何意义的
- GLI 全局解释器所--->保证线程都在同一个cpu上运行
- 【协程】:一个线程,速度很快,从头到尾只有一个线程在运行,不会再起线程;原理:只有一个线程,不再起线程,不去多浪费资源,任务来了,主要利用了异步IO,一个线程来回的切换,性能很好;如:Nginx就是使用了协程
扩展:
协程,只有有个线程在运行,每遇到消耗IO的地方就会立马切换,在切换到另一个,等着这个IO结束了,再去拿到数据,性能比较好;如:nginx就是使用的协程。
任何付出都是值得的,会越来越好