前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Python多线程-手慢无的真相

Python多线程-手慢无的真相

作者头像
唔仄lo咚锵
发布2021-09-14 10:26:01
5130
发布2021-09-14 10:26:01
举报
文章被收录于专栏:blog(为什么会重名,真的醉了)

文章目录

在这里插入图片描述
在这里插入图片描述

我们常说的「手慢无」其实类似多线程同时竞争一个共享资源的结果,要保证结果的唯一正确性,而这让我们从线程(Python)慢慢说起……

线程的概念


线程(Thread)是CPU分配资源的基本单位。一个程序开始运行就变成了一个进程,而一个进程相当于一个或多个线程,使用线程可以实现程序的并发。

一个程序中可以同时运行多个线程,用不同的线程完成不同的任务。

传统的程序设计语言同一时刻只能执行单任务操作,效率很低。比如网络程序在接受数据时发生阻塞,而CPU资源处于闲置状态,只能等到程序接受数据后才能继续运行。

多线程实现后台服务程序可以同时处理多个任务,并不发生阻塞现象。多线程程序设计最大的特点是能够提高程序的执行效率和处理速度。Python程序可同时并行运行多个独立线程。比如开发Email系统,创建一个线程用来接受数据,一个线程用来发送数据,即使发送线程在接受数据时被阻塞,接受数据线程仍然可以运行,互相独立不影响。

Python的线程没有优先级,也不能销毁、停止和挂起、也没有恢复、中断。这和其他语言有所不同。

创建多线程


Python3.X实现多线程的是threading模块,使用它可以创建多线程程序,并且在多线程间进行同步和通讯。 因为是一个模块,所以使用前记得导入:import threading Python有如下两种方式来创建线程。

一、通过threading.Thread()创建 Thread()语法如下:

代码语言:javascript
复制
threading.Thread(group=None,target=None,name=None,args=(),kwargs=(),*,deamon=None)
  • group:必须为None,与ThreadGroup类相关,一般不使用。
  • target:目标函数
  • name:线程名,默认Thread-x(x从1开始)
  • args:为目标函数传递实参、元组
  • kwargs:为目标函数传递关键字参数、字典
  • daemon:用来设置线程是否随主线程退出而退出
代码语言:javascript
复制
import threading
def test(x,y):
    for i in range(x,y):
        print(i)
thread1 = threading.Thread(name='t1',target=test,args=(0,5))
thread2 = threading.Thread(name='t2',target=test,args=(5,10))

thread1.start()
thread2.start()
在这里插入图片描述
在这里插入图片描述

二、通过继承threading.Thread类创建 thread.Thread是一个类,可以使用单继承的方式创建一个自己的子类。

代码语言:javascript
复制
import threading
class mythread(threading.Thread):
    def run(self):  #重写父类run方法
        for i in range(0,5):
            print(i)
thread1 = mythread()
thread2 = mythread()
thread1.start()
thread2.start()
在这里插入图片描述
在这里插入图片描述

如果调用时使用run而不是start,那么run()仅仅时被当作一个普通的函数使用,只有在线程为start时,它才是多线程的一种调用函数。

主线程


介绍主线程前,首先简要介绍下父线程和子线程。如果线程A中启动了一个线程B,那么A就是B的父线程,B就是A的子线程。

Python中,主线程是第一个启动的线程。创建线程时有一个daemon属性可以用来判断主线程,当其值为False时,子线程不会虽主线程退出而退出,反之当其值为True时,如果主线程结束,则它的子线程也会被强制结束。

使用daemon属性几个注意事项:

  • 每个线程都有daemon属性,可以不设置,默认值None
  • 从主线程创建的所有线程不设置daemon属性,默认都是False
  • daemon属性必须在start()之前设置,否则会引发RuntimeError
  • 若子线程不设置daemon属性,就取当前daemon来设置,子线程继承子线程的daemon值,作用和设置None一样
  • daemon=True测试并不适用于IDLE环境中的交互模式或脚本运行模式,因为在该环节中的主线程只有在退出Pyhton IDLE时才终止。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

阻塞线程


多线程提供了一个方法join()来阻塞线程,在一个线程中调用另一个线程的join()方法,调用者将被阻塞,直到被调用线程终止。 语法:join(timeout=None)

在这里插入图片描述
在这里插入图片描述

timeout传参是设置超时值,当线程阻塞时间超过该值后,强制结束这个线程。

在这里插入图片描述
在这里插入图片描述

插播反爬信息 )博主CSDN地址:https://wzlodq.blog.csdn.net/

线程方法


前面提到的start、join等都是threading.Thread类的方法。

方法

说明

run

表示线程活动的方法

start

启动线程

join

等待至线程终止

is_alive

返回线程是否活动

getName

返回线程名称

setName

设置线程名称

代码语言:javascript
复制
import time
import threading
def test():
    time.sleep(3)  #等待3秒
    for i in range(0,5):
        print(i)

thread1 = threading.Thread(target=test)
print('是否活动:',thread1.is_alive())
thread1.start()
print('是否活动:',thread1.is_alive())

print(thread1.getName())# 默认threac-x
thread1.setName('thread1')
print(thread1.getName())
thread1.join()
print('记得一键三连')
在这里插入图片描述
在这里插入图片描述

线程同步

同步的概念


Python应用程序中的多线程可以共享资源,如文件、数据库、内存等。当线程以并发形式访问数据时,共享数据可能会产生冲突。Python引入线程同步的概念,以实现共享数据的一致性。线程同步机制让多个线程有序的访问共享资源,而不是同时操作共享资源。

可以通过购物秒杀的例子来进一步理解同步的概念。比如商品的库存量是1,现在有两个人在平台上同时购买该商品,此时第一个线程查询数据库发现库存量是1可以出售,正准备出售此商品;而同时第二个线程也查询到该商品可以出售并且立即点击购买,这时线程1执行购买时,出现出售两次的错误,大于原库存量1。这就是由于数据不同步导致的错误。(手慢无

Python中的锁


Python中的threading模块提供了RLock锁(可重入锁)解决方案。一个时间只能让一个线程操作语句放到Rlockacquire()方法上锁和release()方法解锁。

代码语言:javascript
复制
import threading
class mythread(threading.Thread):
    def run(self):
        global value  # 全局变量
        lock.acquire()  # 上锁
        value+=10  # 设置值
        print('%s:%d'%(self.name,value))  # 读取值
        lock.release()  #  解锁
        
value = 0  # 初始化
lock=threading.RLock()  # 创建可重入锁
thread = []  # 存放线程
for i in range(3):  # 创建3个线程
    thread.append(mythread())
for i in thread:  # 开启线程
    i.start()
在这里插入图片描述
在这里插入图片描述

上述代码中,创建了3个线程,为了读取value值时不产生错误,保证输出值正确,使用了RLock锁将设置值和读取值锁起来,以保证线程的同步。

Python中的条件锁


Python的threading还提供了一个方法Conditing(),称为Python中的条件变量。换句话说,这个条件变量必须与一个锁关联,所以也称为条件锁,用于比较复杂的同步。

比如一个线程上锁后、解锁前因为某一条件一直阻塞着,所以就一直解不开锁,其他线程也会一直获取不了锁而导致被迫阻塞着,即所谓的死锁。

这种情况下,变量锁可以让该线程先解锁,然后阻塞着,等待条件满足了再重新唤醒并上锁,这样就不会因为一个线程有问题而影响其他线程了。

条件锁的原理跟设计模式的生产者/消费者模式类似。生产者是一段用于生产的内容,生产的成果供消费者消费,这中间设计一个缓存池用来存储数据,称为仓库。

  • 生产者仅仅在仓库未满时生产,仓库满则停止生产。
  • 消费者仅仅在仓库有产品时才能消费,空仓则等待。
  • 当消费者发现仓库没有产品时可通知生产者生产。
  • 生产者生产可消费产品后,应该通知消费者去消费。

条件锁常用方法:

方法

说明

acquire

调用关联锁相关方法

release

解锁

wait

使线程进入等待池等待通知并解放锁,使用前须获得锁定否则报错

notify

从等待池挑选一个线程并通知,收到通知的线程将自动调用acquire()尝试获得锁定(进入锁定池);其他线程不会释放锁定,使用前须获得锁定否则报错

notifyAll

通知等待池中所有线程,这些线程都将进入锁定吃尝试获得锁定,调用这个方法不会释放锁定,使用前须获得锁定否则报错

以生产者/消费者为例:

代码语言:javascript
复制
import time
import threading

products = []
condition = threading.Condition()

class Consumer(threading.Thread):  # 消费者
    def consume(self):  # 消费
        global condition
        global products
        condition.acquire()  # 上锁
        if len(products) == 0:  # 判空
            condition.wait()  # 进入等待池等待通知
            print('消费者:没有产品了')
        products.pop()  # 消费一个产品
        print('消费者:已消费一个产品,剩余可消费产品数为'+str(len(products)))
        condition.notify()  # 通知
        condition.release()  # 解锁
    def run(self):
        for i in range(0,10):
            time.sleep(3)  # 设3秒消费一个产品
            self.consume()
class Producer(threading.Thread):  # 生产者
    def produce(self):
        global condition
        global products
        condition.acquire()  # 设置条件锁
        if len(products) == 5:  # 满仓
            condition.wait()  # 进入等待池等待通知
            print('生产者:已满仓,停止生产')
        products.append(1)  # 生产一个产品
        print('生产者:已生产一个产品,剩余可消费产品数为'+str(len(products)))
        condition.notify()  # 通知
        condition.release()  #解锁
    def run(self):
        for i in range(0,10):
            time.sleep(1)  # 设1秒生产一个产品
            self.produce()
producer = Producer()
consumer = Consumer()
producer.start()
consumer.start()
producer.join()
consumer.join()

上述代码用time.sleep()来控制生产和消费的时间,当产品生产数量达到上限时就停止生产,并调用wait等待线程通知;当剩余可消费产品为0时也停止消费,等待线程通知。

在这里插入图片描述
在这里插入图片描述

小结


处理大批流程都类似的程序时,使用多线程可以有效节省时间,耗费的不过时一些计算机资源,是典型的以资源换时间,以目前计算机的性能来看,大多都是性能过剩的,理由剩余的计算机资源来节省时间非常合算。

使用多线程是要注意锁的使用,使用锁来保护共享的资源、数据,避免被其他的线程破坏,一般使用互斥锁就可以应付大多数情况了。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/02/25 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • 线程的概念
  • 创建多线程
  • 主线程
  • 阻塞线程
  • 线程方法
  • 线程同步
    • 同步的概念
      • Python中的锁
        • Python中的条件锁
        • 小结
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档