14 python学习笔记-多线程threading

做自动化测试时,测试的case多,单线程执行测试用例,速度慢,执行的时间长;或在使用Pyhotn或Java对服务端进行压力测试的时候,单线程只能模拟单个用户的行为,此时,我们可以引入多线程、多进程去执行测试用例,进行压力测试。
一、进程与线程基本概念

1、进程:

进程(英语:process),是指计算机中已运行的程序。你可以理解成一个任务就是一个进程,比如打开一个浏览器就是启动一个浏览器进程,打开一个记事本就启动了一个记事本进程,打开两个记事本就启动了两个记事本进程,打开一个Word就启动了一个Word进程。进程是很多资源的集合。多进程多用于CPU密集型任务(大量的计算)。

2、线程

线程(英语:thread)是操作系统能够进行运算调度的最小单位,是进程里边具体干活的,它被包含在进程之中,是进程中的实际运作单位。一个进程中可以并发多个线程,每条线程并行执行不同的任务。比如Word,它可以同时进行打字、拼写检查、打印等子任务,这些进程内的这些“子任务”称为线程(Thread)。多线程多用于IO密集型任务(磁盘数据的读取和写入,网络的IO数据传输)。

注:python中的多线程并不是真正意义上的多线程,因为python解释器使用了GIL的全局解释锁

GIL全局解释器锁:不能利用多核CPU,只能运行在一个cpu上面,但是你在运行程序的时候,看起来好像还是在一起运行的,是因为操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务3,执行0.01秒……这样反复执行下去。表面上看,每个任务都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样。这个叫做上下文切换。

二、多线程threading

  • Python通过两个标准库thread和threading提供对线程的支持。thread提供了低级别的、原始的线程以及一个简单的锁.Threading模块封装了一些常用的方法,初学者直接学这个模块就行了。
  • Python中使用线程有两种方式:函数(函数式)或者用类(继承式、封装式)来包装线程对象
  • threading.Thread里面几个参数介绍:
    class Thread(_Verbose)
       
       __init__(self, group=None, target=None, name=None, args=(), kwargs=None, verbose=None)
           
           *group*:group参数必须为空,参数group是预留的,用于将来扩展;
     
         参数args和kwargs分别表示调用target时的参数列表和关键字参数。
           
           *target*: 参数target是一个可调用对象(也称为活动[activity]),在线程启动后执行
           
           *name*: 参数name是线程的名字。默认值为“Thread-N“,N是一个数字。
          
           *args*:传递给线程函数target的参数,他必须是个tuple类型.
           
           *kwargs*:kwargs表示关键字参数。字典类型 {}.

     

1、函数式多线程

下面是一个简单的函数式多线程:

 1 import threading
 2 
 3 #定义每个线程要运行的函数
 4 def down_load(num):
 5     print('等待下载完第%d个文件'%num)
 6 
 7 def music():
 8     print('听着音乐')
 9 
10 def eat(food,drink):
11     print('吃着%s,喝着%s'%(food,drink))
12 
13 if __name__ == '__main__':
14     #创建线程组
15     threads=[]
16     #创建线程t1,t2,t3
17     # 1、函数不传参数
18     t1=threading.Thread(target=music)
19     threads.append(t1)
20     # 2、传kwargs参数
21     t2=threading.Thread(target=eat,kwargs={'food':'炸鸡','drink':'可乐'})
22     threads.append(t2)
23     #3、带参数的用args传元组类型(参数最后多加一个逗号“,”要不然会报错)
24     t3 = threading.Thread(target=down_load,args=(1,))
25     threads.append(t3)
26 
27     #启动线程t3,t2,t1
28     for t in threads:
29         t.start()

=========执行结果==================

Thu Nov 21 23:56:36 2019听着音乐
Thu Nov 21 23:56:36 2019吃着炸鸡,喝着可乐
Thu Nov 21 23:56:36 2019等待下载完第1个文件

2、继承式多线程

下面是另一种启动多线程的方式,继承式

1.start()方法 开始线程活动。

对每一个线程对象来说它只能被调用一次,它安排对象在一个另外的单独线程中调用run()方法(而非当前所处线程)。

当该方法在同一个线程对象中被调用超过一次时,会引入RuntimeError(运行时错误)。

2.run()方法 代表了线程活动的方法。

你可以在子类中重写此方法。标准run()方法调用了传递给对象的构造函数的可调对象作为目标参数,如果有这样的参数的话,顺序和关键字参数分别从args和kargs取得
start()和run()方法的区别
 1 import threading,time
 2 
 3 
 4 #1.先写一个执行函数,用来实现做某件事情,不同的人在做不同的事用两个参数people,food。
 5 def cook(people,food):
 6     print('%s%s正在做%s'%(time.ctime(),people,food))
 7 
 8 
 9 #2.使用Threading模块创建线程,直接从threading.Thread继承,然后重写__init__方法和run方法
10 class MyThread(threading.Thread):#继承父类threading.Thread
11     def __init__(self,people,food,name):
12         '''重写threading.Thread初始化内容'''
13         threading.Thread.__init__(self)
14         self.threadName=name
15         self.people=people
16         self.food=food
17 
18     def run(self):  # 把要执行的代码写到run函数里面 线程在创建后会直接运行run函数
19         '''重写run方法'''
20         print('开始线程:'+self.threadName)
21 
22         cook(self.people,self.food) #执行任务
23         print('等待中\n')
24         print('结束线程'+self.name)
25 
26 if __name__ == '__main__':
27     #创建新线程,并将线程添加到线程组
28     threads=[]
29     t1=MyThread('小米','红烧鱼','thread-1')
30     threads.append(t1)
31     t2=MyThread('小明','水煮牛肉','thread-2')
32     threads.append(t2)
33     t3=MyThread('小美','佛跳墙','thread-3')
34     threads.append(t3)
35 
36     #启动线程
37     for t in threads:
38         t.start()
39 
40     print(time.sleep(1))
41     print('退出主线程')

==============执行结果==============

开始线程:thread-1
Fri Nov 22 00:31:37 2019小米正在做红烧鱼
开始线程:thread-2
等待中

Fri Nov 22 00:31:37 2019小明正在做水煮牛肉
开始线程:thread-3
Fri Nov 22 00:31:37 2019小美正在做佛跳墙
等待中

结束线程Thread-3
退出主线程
结束线程Thread-1
等待中

结束线程Thread-2

从以上执行结果看,主线程已退出,子线程Thread-1和Thread-2还在运行,这就需要用到后面讲的等待线程和守护线程了

三、守护线程(setDaemon())

1、定义 :主线程结束了,子线程必须也跟着结束,这些子线程叫做守护线程

  • 主线程中,创建了子线程thread1和thread2、thread3,并且在主线程中调用了thread.setDaemon(),这个的意思是,把主线程设置为守护线程,这时候,要是主线程执行结束了,就不管子线程是否完成,一并和主线程退出.(注意:必须在start()方法调用之前设置,如果不设置为守护线程,程序会被无限挂起。)
  • 线程有一个布尔属性叫做daemon。表示线程是否是守护线程,默认取否。当程序中的线程全部是守护线程时,程序才会退出。只要还存在一个非守护线程,程序就不会退出。
  • 主线程是非守护线程。
  • setDaemon(True)此方法里面参数设置为True才会生效
 1 import threading,time
 2 
 3 
 4 #1.先写一个执行函数,用来实现做某件事情,不同的人在做不同的事用两个参数people,food。
 5 def cook(people,food):
 6     print('%s%s正在做%s'%(time.ctime(),people,food))
 7 
 8 
 9 #2.使用Threading模块创建线程,直接从threading.Thread继承,然后重写__init__方法和run方法
10 class MyThread(threading.Thread):#继承父类threading.Thread
11     def __init__(self,people,food,name):
12         '''重写threading.Thread初始化内容'''
13         threading.Thread.__init__(self)
14         self.threadName=name
15         self.people=people
16         self.food=food
17 
18     def run(self):  # 把要执行的代码写到run函数里面 线程在创建后会直接运行run函数
19         '''重写run方法'''
20         print('开始线程:'+self.threadName)
21 
22         cook(self.people,self.food) #执行任务
23         print('等待中\n')
24         print('结束线程'+self.name)
25 
26 if __name__ == '__main__':
27     #创建新线程,并将线程添加到线程组
28     threads=[]
29     t1=MyThread('小米','红烧鱼','thread-1')
30     threads.append(t1)
31     t2=MyThread('小明','水煮牛肉','thread-2')
32     threads.append(t2)
33     t3=MyThread('小美','佛跳墙','thread-3')
34     threads.append(t3)
35 
36     #启动线程
37     for t in threads:
38         t.setDaemon(True)  #守护线程在启动线程之前加上
39         t.start()
40 
41     # time.sleep(0.5)
42     print('退出主线程')
43 ===============================================执行结果=================================================== 44 =====================================主线程结束,未运行的子线程也结束不再运行==================================== 45 开始线程:thread-1 46 Mon Nov 25 17:54:04 2019小米正在做红烧鱼 47 等待中 48 49 结束线程Thread-1 50 开始线程:thread-2 51 Mon Nov 25 17:54:04 2019小明正在做水煮牛肉 52 等待中 53 54 结束线程Thread-2 55 开始线程:thread-3 56 Mon Nov 25 17:54:04 2019小美正在做佛跳墙 57 等待中 58 59 退出主线程

可以看到,加上守护线程后,主线程结束,未运行的子线程也结束不再运行

四、线程等待join(timeout)

  • 如果想让主线程等待子线程结束后再运行的话,就需要用到join(),此方法是在start之后(与setDaemon相反)
  • join(timeout)此方法有个timeout参数,是线程超时时间设置。
 1 import threading,time
 2 
 3 
 4 #1.先写一个执行函数,用来实现做某件事情,不同的人在做不同的事用两个参数people,food。
 5 def cook(people,food):
 6     print('%s%s正在做%s'%(time.ctime(),people,food))
 7 
 8 
 9 #2.使用Threading模块创建线程,直接从threading.Thread继承,然后重写__init__方法和run方法
10 class MyThread(threading.Thread):#继承父类threading.Thread
11     def __init__(self,people,food,name):
12         '''重写threading.Thread初始化内容'''
13         threading.Thread.__init__(self)
14         self.threadName=name
15         self.people=people
16         self.food=food
17 
18     def run(self):  # 把要执行的代码写到run函数里面 线程在创建后会直接运行run函数
19         '''重写run方法'''
20         print('开始线程:'+self.threadName)
21 
22         cook(self.people,self.food) #执行任务
23         print('等待中\n')
24         print('结束线程'+self.name)
25 
26 if __name__ == '__main__':
27     #创建新线程,并将线程添加到线程组
28     threads=[]
29     t1=MyThread('小米','红烧鱼','thread-1')
30     threads.append(t1)
31     t2=MyThread('小明','水煮牛肉','thread-2')
32     threads.append(t2)
33     t3=MyThread('小美','佛跳墙','thread-3')
34     threads.append(t3)
35 
36     #启动线程
37     for t in threads:
38         # t.setDaemon(True)  #守护线程在启动线程之前加上
39         t.start()
40         t.join()  #等待线程,所有子线程结束,主线程才结束
41 
42     # time.sleep(0.5)
43     print('退出主线程')
=============================执行结果==========================================

开始线程:thread-1
Mon Nov 25 18:00:10 2019小米正在做红烧鱼
等待中

结束线程Thread-1
开始线程:thread-2
Mon Nov 25 18:00:10 2019小明正在做水煮牛肉
等待中

结束线程Thread-2
开始线程:thread-3
Mon Nov 25 18:00:10 2019小美正在做佛跳墙
等待中

结束线程Thread-3
退出主线程

 五、线程锁Lock

定义:线程锁就是,很多线程一起在操作一个数据的时候,可能会有问题,就要把这个数据加个锁,同一时间只能有一个线程操作这个数据。

 1 #多个线程操作同一个数据的时候,就得加锁
 2 import threading
 3 
 4 num = 0
 5 
 6 lock = threading.Lock() #申请一把锁
 7 
 8 def add():
 9     global num
10     # lock.acquire()#加锁
11     # num+=1
12     # lock.release()#解锁  #死锁
13     with lock:#简写,用with也会帮你加锁,解锁
14         num+=1
15 
16 for i in range(20):
17     t = threading.Thread(target=add,)
18     t.start()
19 #循环条件:当运行的线程数不等于1,效果与线程等待join的作用一致
20 while threading.activeCount()!=1:  
21     pass
22 
23 print('over',num)

运行结果:over 20

六、多线程爬虫下载图片实例

 

 1 import requests,time,threading
 2 from hashlib import md5
 3 result_list = {}
 4 def down_load_pic(url):
 5     req = requests.get(url)
 6     m = md5(url.encode())
 7     file_name = m.hexdigest()+'.png'
 8     with open(file_name ,'wb') as fw:
 9         fw.write(req.content)
10     result_list[file_name] = threading.current_thread()
11 
12 url_list = ['http://www.nnzhp.cn/wp-content/uploads/2019/10/f410afea8b23fa401505a1449a41a133.png',
13             'http://www.nnzhp.cn/wp-content/uploads/2019/11/481b5135e75c764b32b224c5650a8df5.png',
14             'http://www.nnzhp.cn/wp-content/uploads/2019/11/b23755cdea210cfec903333c5cce6895.png',
15             'http://www.nnzhp.cn/wp-content/uploads/2019/11/542824dde1dbd29ec61ad5ea867ef245.png']
16 
17 #多线程运行时间
18 start_time = time.time()
19 for url in url_list:
20     t = threading.Thread(target=down_load_pic,args=(url,))
21     t.start()
22 
23 while threading.activeCount()!=1:
24     pass
25 end_time = time.time()
26 print(end_time - start_time)
27 
28 #下面是单线程的运行时间
29 # start_time = time.time()
30 # for url in url_list:
31 #     down_load_pic(url)
32 # end_time = time.time()
33 # print(end_time - start_time)

 

七、线程池threadpool

1、内置的multiprocessing模块里面也有线程池,那么它和我们常常在代码里面看到的threadpool模块有什么区别呢?

  • threadpool模块是一个很老的实现python线程池的模块,是第三方库;
  • threadpool官方说它已经被废弃了,虽然它在Python2和Python3里面还能用。
  • 官方建议用multiprocessing和Python3里面的asyncio替代它。

2、threadpool模块的使用介绍

  1. 引入threadpool模块
  2. 定义线程函数   
  3. 创建线程 池threadpool.ThreadPool()   
  4. 创建需要线程池处理的任务即threadpool.makeRequests()   
  5. 将创建的多个任务put到线程池中,threadpool.putRequest   
  6. 等到所有任务处理完毕theadpool.pool()

 

3、线程池使用实例

 1 import threadpool
 2 import requests,threading
 3 from hashlib import md5
 4 def down_load_pic(url):
 5     print(threading.current_thread())
 6     req = requests.get(url)
 7     m = md5(url.encode())
 8     with open( m.hexdigest()+'.png','wb') as fw:
 9         fw.write(req.content)
10 url_list = ['http://www.nnzhp.cn/wp-content/uploads/2019/10/f410afea8b23fa401505a1449a41a133.png',
11             'http://www.nnzhp.cn/wp-content/uploads/2019/11/481b5135e75c764b32b224c5650a8df5.png',
12             'http://www.nnzhp.cn/wp-content/uploads/2019/11/b23755cdea210cfec903333c5cce6895.png',
13             'http://www.nnzhp.cn/wp-content/uploads/2019/11/542824dde1dbd29ec61ad5ea867ef245.png']
14 #实例化一个线程池,设置线程池中线程个数为20
15 pool = threadpool.ThreadPool(20)
16 #分配数据,将每个任务放到线程池中,等待线程池中线程各自读取任务,然后进行处理
17 reqs = threadpool.makeRequests(down_load_pic,url_list)
18 # for req in reqs:
19 #     pool.putRequest(req)
20 [pool.putRequest(req) for req in reqs]  #将创建的多个任务put到线程池中
21 print(threading.activeCount())
22 pool.wait() #等待
23 print('end')

 

posted @ 2019-11-22 00:42  落落mo  阅读(283)  评论(0编辑  收藏  举报