• 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('等待中
    ')
    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('等待中
    ')
    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('等待中
    ')
    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')
  • 相关阅读:
    Authentication for the REST APIs
    Authentication for the REST APIs
    泛型转Datatable
    Web API 返回json文件的2中不用方式
    Robotframework自定义关键字库
    python通过接口上传图片造测试数据
    robot framework(2) 环境搭建
    RobotFrameWork(1) 关键字驱动测试框架
    python发送带附件的邮件
    解决adb连接海马玩模拟器
  • 原文地址:https://www.cnblogs.com/cocomoly/p/11909390.html
Copyright © 2020-2023  润新知