【进阶16】Python多线程实战案例

发布时间 2023-10-07 22:25:17作者: 橙子全栈测试笔记

一、Python实现多线程的几种方式

_thread:模块提供了基本的线程和互斥锁支持;更底层的的线程管理实现模块
threading:threading模块则通过封装_thread,提供了更加全面的线程使用方法。
_thread案例:
# *coding:utf-8 *
#用_thread启动多个线程完成任务
import  _thread
import threading
#板砖的动作
import time
global brick_list
brick_list = ["砖头1", "砖头2", "砖头3", "砖头4", "砖头5", "砖头6", "砖头7","砖头8", "砖头9", "砖头10", "砖头11", "砖头12", "砖头13", "砖头14", "砖头15", "砖头16", "砖头17", "砖头18", "砖头19", "砖头20", "砖头21", "砖头22", "砖头23", "砖头24", "砖头25", "砖头26", "砖头27", "砖头28", "砖头29", "砖头30","砖头31", "砖头32", "砖头33", "砖头34", "砖头35", "砖头36", "砖头37", "砖头38", "砖头39", "砖头40", "砖头41", "砖头42", "砖头43", "砖头44", "砖头45", "砖头46", "砖头47", "砖头48", "砖头49", "砖头50","砖头51", "砖头52", "砖头53", "砖头54", "砖头55", "砖头56", "砖头57", "砖头58", "砖头59", "砖头60","砖头61", "砖头62", "砖头63", "砖头64", "砖头65", "砖头66", "砖头67", "砖头68", "砖头69", "砖头70", "砖头71", "砖头72", "砖头73", "砖头74", "砖头75", "砖头76", "砖头77", "砖头78", "砖头79", "砖头80", "砖头81", "砖头82", "砖头83", "砖头84", "砖头85", "砖头86", "砖头87", "砖头88", "砖头89", "砖头90","砖头91", "砖头92", "砖头93", "砖头94", "砖头95", "砖头96", "砖头97", "砖头98", "砖头99", "砖头100"]
def action():
    while True:
        if len(brick_list) ==0:
            break
        brick_list.pop()  #搬砖
        print(threading.currentThread(),"还剩%s" % (brick_list))
        time.sleep(0.1)

def main():
    #自己一个人搬
    # action()
    #请一个人
    # _thread.start_new_thread(action,())

    #请100个人,多个子进程运行
    for i in range(100):
        _thread.start_new_thread(action, ())
    time.sleep(10)



if __name__ == '__main__':
    main()
关键代码解释:
_thread.start_new_thread(action, ())
启动一个新的线程,执行action函数,  
threading案例:
# *coding:utf-8 *
import  _thread
import threading
#板砖的动作
import time
global brick_list
brick_list = ["砖头1", "砖头2", "砖头3", "砖头4", "砖头5", "砖头6", "砖头7","砖头8", "砖头9", "砖头10", "砖头11", "砖头12", "砖头13", "砖头14", "砖头15", "砖头16", "砖头17", "砖头18", "砖头19", "砖头20", "砖头21", "砖头22", "砖头23", "砖头24", "砖头25", "砖头26", "砖头27", "砖头28", "砖头29", "砖头30","砖头31", "砖头32", "砖头33", "砖头34", "砖头35", "砖头36", "砖头37", "砖头38", "砖头39", "砖头40", "砖头41", "砖头42", "砖头43", "砖头44", "砖头45", "砖头46", "砖头47", "砖头48", "砖头49", "砖头50","砖头51", "砖头52", "砖头53", "砖头54", "砖头55", "砖头56", "砖头57", "砖头58", "砖头59", "砖头60","砖头61", "砖头62", "砖头63", "砖头64", "砖头65", "砖头66", "砖头67", "砖头68", "砖头69", "砖头70", "砖头71", "砖头72", "砖头73", "砖头74", "砖头75", "砖头76", "砖头77", "砖头78", "砖头79", "砖头80", "砖头81", "砖头82", "砖头83", "砖头84", "砖头85", "砖头86", "砖头87", "砖头88", "砖头89", "砖头90","砖头91", "砖头92", "砖头93", "砖头94", "砖头95", "砖头96", "砖头97", "砖头98", "砖头99", "砖头100"]
def action():
    while True:
        if len(brick_list) ==0:
            break
        brick_list.pop()  #搬砖
        print(threading.currentThread(),"还剩%s" % (brick_list))
        time.sleep(0.1)

def main():
    #启动两个线程搬砖
    # t1=threading.Thread(target=action,args=())
    # t2=threading.Thread(target=action,args=())
    # t1.start()
    # t2.start()
    #启动100个线程搬砖
    for i in range(100):
        temp=threading.Thread(target=action,name="thread"+ str(i),args=())
        temp.start()
    time.sleep(10)

if __name__ == '__main__':
    main()
关键代码解释:
threading.Thread(target=action,name="thread1", args=())
创建一个线程,这个线程默认没有启动,需要继续调用start()来启动
例如:threading.Thread(target=action,name="thread1", args=()).start()
其中,target用要执行的任务函数,name是线程名,args是传递给执行的任务函数值

二、Threading多线程编程

2.1线程阻塞

Thread().join()
作用:阻塞主线程的运行,让主线程等待所有子线程运行结束之后,再继续运行
# *coding:utf-8 *
import  _thread
import threading
#板砖的动作
import time
global brick_list
brick_list = ["砖头1", "砖头2", "砖头3", "砖头4", "砖头5", "砖头6", "砖头7","砖头8", "砖头9", "砖头10", "砖头11", "砖头12", "砖头13", "砖头14", "砖头15", "砖头16", "砖头17", "砖头18", "砖头19", "砖头20", "砖头21", "砖头22", "砖头23", "砖头24", "砖头25", "砖头26", "砖头27", "砖头28", "砖头29", "砖头30","砖头31", "砖头32", "砖头33", "砖头34", "砖头35", "砖头36", "砖头37", "砖头38", "砖头39", "砖头40", "砖头41", "砖头42", "砖头43", "砖头44", "砖头45", "砖头46", "砖头47", "砖头48", "砖头49", "砖头50","砖头51", "砖头52", "砖头53", "砖头54", "砖头55", "砖头56", "砖头57", "砖头58", "砖头59", "砖头60","砖头61", "砖头62", "砖头63", "砖头64", "砖头65", "砖头66", "砖头67", "砖头68", "砖头69", "砖头70", "砖头71", "砖头72", "砖头73", "砖头74", "砖头75", "砖头76", "砖头77", "砖头78", "砖头79", "砖头80", "砖头81", "砖头82", "砖头83", "砖头84", "砖头85", "砖头86", "砖头87", "砖头88", "砖头89", "砖头90","砖头91", "砖头92", "砖头93", "砖头94", "砖头95", "砖头96", "砖头97", "砖头98", "砖头99", "砖头100"]
def action():
    while True:
        if len(brick_list) ==0:
            break
        brick_list.pop()  #搬砖
        print(threading.currentThread(),"还剩%s" % (brick_list))
        time.sleep(0.1)

def main():

    t1=threading.Thread(target=action,name="1号码农",args=(),daemon=True)   #daemon=True为守护进程,当主线程结束也会随之结束
    t1.start()
    t1.join()   #阻塞主线程


if __name__ == '__main__':
    main()

2.2 守护线程和非守护线程

获取Thread对象后,通过Thread对象中__init__方法中的daemon参数来设置守护线程和非守护线程,默认是非守护线程。
守护线程:其他线程都运行结束后,守护线程立即结束
非守护线程:无论其他线程有没有运行结束,本线程都必须正常运行结束后才会结束。
import threading
import time
from  time import sleep

def action(max):
    for i in range(max):
        print(threading.currentThread().name+"{}次循环\n".format(i))
    time.sleep(1)


def main():

    t = threading.Thread(target=action, args=(10000,), name='后台线程')
    t.daemon = True     #设置为守护线程
    # 启动后台线程
    t.start()
    for i in range(10):
        print(threading.current_thread().name+"主线程循环了{}次".format(i))
    print("主线程运行结束了")


if __name__ == '__main__':
    main()
2.3 线程锁(Lock)和信号量
线程锁(Lock): 为了防止线程与线程之间资源共享导致的线程安全问题,我们可以对访
问的资源加上锁,这个锁就是线程锁。
信号量:信号量是指控制有多少个线程可以访问的相同资源
 
2.3.1线程锁案例:
Lock对象方法:
acquire()   获取锁
release()  释放锁
import threading
from time import sleep
result = 0

#获取锁对象
lock=threading.Lock()
def add():
    global result
    lock.acquire()  #加锁
    for i in range(1000000):
        result = result + 1
    lock.release()  #解锁
    print("%s 的resul的结果为:%s" % (threading.current_thread().name,result))
def main():
     thread_list = []
     for i in range(3):
         thread_list.append(threading.Thread(target=add, name="t" + str(i))) # 初始化3个线程,
     #并把线程对象添加到thread_list中
     for i in range(3):
         thread_list[i].start() # 启动thread_list中的3个线程
if __name__ == '__main__':
 main()
2.3.2信号量案例:
import threading,time
semaphore=threading.Semaphore(500) #只允许500个人进入地铁站,执行运输任务

def action(counts):
    print("{}乘客来到地铁站".format(counts))
    semaphore.acquire()  # 获得信号量:信号量减一
    print("{}乘客已进站".format(counts))
    time.sleep(10)
    semaphore.release() #释放信号量:信号量加一
    print("{}乘客已出站".format(counts))

def main():
 for i in range(10000):
    t = threading.Thread(target=action, args=(i,))
    t.start()
if __name__ == '__main__':
 main()

三、队列和线程池  

1、队列用法:

from queue import Queue
#创建队列
q = Queue(maxsize=10)
队列常见操作方法:
Queue.qsize() 返回队列的大小
Queue.empty() 如果队列为空,返回True,反之False
Queue.full() 如果队列满了,返回True,反之False
Queue.full 与 maxsize 大小对应
Queue.get([block[, timeout]])获取队列,timeout等待时间
Queue.get_nowait() 相当Queue.get(False)
Queue.put(item) 写入队列,timeout等待时间
Queue.put_nowait(item) 相当Queue.put(item, False)
Queue.task_done() 在完成一项工作之后,Queue.task_done()函数向任务已经完成的队列
发送一个信号
Queue.join() 实际上意味着等到队列为空,再执行别的操作 

案例:

import random
import threading
from queue import Queue
import time

q=Queue(10) #启动一个队列大小是10,能放10个菜
food_menu = ["佛跳墙", "红烧狮子头", "东坡肉", "蚂蚁 上树", "鱼香肉丝", "麻婆豆腐", "鱼子酱", "意大利面", "土豆丝炒肉", "银耳羹", "窝窝头", "糖醋排骨"]

def make_lunch():
    """服务端"""
    print("开始做菜")
    while True:
        if q.full():  #队列满了
            print("菜做好了")
            q.join()  #等待食客吃饭
        food_number=random.randint(0,len(food_menu)-1)
        q.put(food_menu[food_number])  # 写入队列
        print("做了:", food_menu[food_number])
        time.sleep(0.5)

def ear_lunch():

    time.sleep(10) # 等才做好了再开始吃
    while True:
        if q.empty():  #队列消息为空
            print("吃完了再来一顿")
            time.sleep(10)
        else:
            food_name=q.get() #获取队列消息
            print("吃了",food_name)
            q.task_done()
def main():
    #定义两个消息队列,并开启服务
    cooker=threading.Thread(target=make_lunch,args=(),name="cooker")
    eater=threading.Thread(target=ear_lunch,args=(),name="cooker")
    cooker.start()
    eater.start()


if __name__ == '__main__':
    main()

2、线程池:

顾名思义就是存放线程的池子,线程池可以控制线程的启动数量,从而达到节省系统资源的目的
作用:可以控制要启动的线程数量。
线程池与信号量Semaphore的区别:
  线程池控制的是线程数量,而信号量控制的是并发数量。
  超过信号量规定数量的线程,已经被启动了,只是状态是挂起。而线程池中,超过了线
  程池规定数量的线程,还没有启动,只能等待启动。

使用方式:

threadpool库是第三方的库,所以必须要进行安装

#线程池案例
import  threading
import threadpool
import time
brick_list = ["砖头1", "砖头2", "砖头3", "砖头4", "砖头5", "砖头6", "砖头7","砖头8", "砖头9", "砖头10", "砖头11", "砖头12", "砖头13", "砖头14", "砖头15", "砖头16", "砖头17", "砖头18", "砖头19", "砖头20", "砖头21", "砖头22", "砖头23", "砖头24", "砖头25", "砖头26", "砖头27", "砖头28", "砖头29", "砖头30","砖头31", "砖头32", "砖头33", "砖头34", "砖头35", "砖头36", "砖头37", "砖头38", "砖头39", "砖头40", "砖头41", "砖头42", "砖头43", "砖头44", "砖头45", "砖头46", "砖头47", "砖头48", "砖头49", "砖头50","砖头51", "砖头52", "砖头53", "砖头54", "砖头55", "砖头56", "砖头57", "砖头58", "砖头59", "砖头60","砖头61", "砖头62", "砖头63", "砖头64", "砖头65", "砖头66", "砖头67", "砖头68", "砖头69", "砖头70", "砖头71", "砖头72", "砖头73", "砖头74", "砖头75", "砖头76", "砖头77", "砖头78", "砖头79", "砖头80", "砖头81", "砖头82", "砖头83", "砖头84", "砖头85", "砖头86", "砖头87", "砖头88", "砖头89", "砖头90","砖头91", "砖头92", "砖头93", "砖头94", "砖头95", "砖头96", "砖头97", "砖头98", "砖头99", "砖头100"]

def action(brick):
    print(threading.currentThread().name+"搬出了: ",brick)
    time.sleep(0.2)

def main():
    tp=threadpool.ThreadPool(5)  #设置5个线程
    requests=threadpool.makeRequests(action,brick_list)
    for req in requests: tp.putRequest(req)
    tp.wait()

if __name__ == '__main__':
    main()

四、tomorrow库:多线程并发测试库

pip install tomorrow
具体案例:

#使用tomorrow来完成“听歌、聊天、吃饭”三件事儿
import time
from random import random

from tomorrow import threads
import threading
@threads(10) # 最多开启10个线程完成任务
def task( task_name):
    print( threading.current_thread().name +"正在:", task_name)
if __name__ == '__main__':
 task_name_list = ["砖头1", "砖头2", "砖头3", "砖头4", "砖头5", "砖头6", "砖头7","砖头8", "砖头9", "砖头10", "砖头11", "砖头12", "砖头13", "砖头14", "砖头15", "砖头16", "砖头17", "砖头18", "砖头19", "砖头20", "砖头21", "砖头22", "砖头23", "砖头24", "砖头25", "砖头26", "砖头27", "砖头28", "砖头29", "砖头30","砖头31", "砖头32", "砖头33", "砖头34", "砖头35", "砖头36", "砖头37", "砖头38", "砖头39", "砖头40", "砖头41", "砖头42", "砖头43", "砖头44", "砖头45", "砖头46", "砖头47", "砖头48", "砖头49", "砖头50","砖头51", "砖头52", "砖头53", "砖头54", "砖头55", "砖头56", "砖头57", "砖头58", "砖头59", "砖头60","砖头61", "砖头62", "砖头63", "砖头64", "砖头65", "砖头66", "砖头67", "砖头68", "砖头69", "砖头70", "砖头71", "砖头72", "砖头73", "砖头74", "砖头75", "砖头76", "砖头77", "砖头78", "砖头79", "砖头80", "砖头81", "砖头82", "砖头83", "砖头84", "砖头85", "砖头86", "砖头87", "砖头88", "砖头89", "砖头90","砖头91", "砖头92", "砖头93", "砖头94", "砖头95", "砖头96", "砖头97", "砖头98", "砖头99", "砖头100"]

 for i in range(0,len(task_name_list)-1):
    task(task_name_list[i])
    time.sleep(0.2)