zoukankan      html  css  js  c++  java
  • 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')
  • 相关阅读:
    BZOJ 3506 机械排序臂 splay
    BZOJ 2843 LCT
    BZOJ 3669 魔法森林
    BZOJ 2049 LCT
    BZOJ 3223 文艺平衡树 splay
    BZOJ 1433 假期的宿舍 二分图匹配
    BZOJ 1051 受欢迎的牛 强连通块
    BZOJ 1503 郁闷的出纳员 treap
    BZOJ 1096 ZJOI2007 仓库设计 斜率优化dp
    BZOJ 1396: 识别子串( 后缀数组 + 线段树 )
  • 原文地址:https://www.cnblogs.com/cocomoly/p/11909390.html
Copyright © 2011-2022 走看看