python并发


前言

并发和其表现形式之一并行处理是一个广泛而有复杂的话题。如果两个事件互不影响,可以同时并行的处理则两个事件是并发的。本文将讲述在python中最重要的三个并发模型:多线程、多进程、异步编程。

一、多线程

线程:调度执行的最小单位,也叫执行路径,不能独立存在,依赖进程存在一个进程至少有一个线程,叫主线程,而多个线程共享内存(数据共享,共享全局变量),从而极大地提高了程序的运行效率。

threading模块

threading模块支持多线程编写的重要模块。此模块提供了Thread、Lock、RLock、Condition、Event、Timer和Semaphore等类来支持多线程编写。其中最基本的类为Thread,可以通过该类创建线程并控制线程运行。
创建线程的两种方式:

  • 为.Thread类传递运行函数。
  • 继承Thread并在派生类中重写构造方法和run方法。
import time
def countdown(n):
    while n>0:
        print("T-minus",n)
        n-=1
        time.sleep(1)
from threading import Thread
t=Thread(target=countdown,args=(10,))
t.start()

关于Thread对象的一些说明:

t=Thread(target=countdown,args=(10,),daemon=False,)

target接收函数名,args:函数参数,以元组的形式传递,daemon:是否为守护线程,默认为False。
当创建一个线程实例时,在调用它的start()方法之前,线程不会立即执行。线程开始运行之后可以使用t.is_alive()来判断线程是否在运行。用上面的方法运行线程主线程并没有等待自定义线程运行完毕,而是自己运行完成,然后子线程再完成。如果我们需要子线程运行完毕之后主线程再运行完毕可以使用t.join([timeout])

t=Thread(target=countdown,args=(10,))
start1=time.time()
t.start()
t.join()
end=time.time()
print(end-start1)# 10.097468376159668

谈到这我们依然在某个点停止线程,如果我们希望这么做的话,可以考虑使用第二种创建线程的方法。

import time
class CountdownTask():
    def __init__(self):
        self.running=True
    def terminate(self):
        self.running=False
    def run(self,n):
        while self.running and n>0:
            print('T-minus',n)
            n-=1
            time.sleep(1)
c=CountdownTask()
from threading import Thread
t=Thread(target=c.run,args=(10,))
t.start()
time.sleep(5)
c.terminate()

说明:t.start()自动调用run()方法,启动线程,执行线程代码

协程同步技术

多个线程会同步相互协作地完成同一件任务。但线程的一个关键特性是每个线程都是独立运行且状态不可预测, 共享资源会被随机的破坏掉,已及产生我们称之为竞态条件的奇怪行为。这意味着必须保护共享数据。前面说的Lock、RLock、Condition、Event、Timer和Semaphore均是为此准备的。

显式加锁、隐式加锁

在介绍Lock等类之前我们先介绍两种不同的加锁方式。

import threading
class SharedCounter:
    def __init__(self,inital_value=0):
        self._value=inital_value
        self._value_lock=threading.Lock()
    def incr(self,delata=1):
        self._value_lock.acquire()
        self._value+=delata
        self._value_lock.release()
    def decr(self,delata=1):
        with self._value_lock:
            self._value-=delata

不管是incr还是decr中都有加锁,不过加锁的方式不同,前者是显式使用锁,后者是隐式,后者更加优雅,也不容易出错。

RLock、Lock

Lock是比较低价的同步原语,当被锁定以后不属于特定的线程,其提供了两种方法acquire()它锁定锁的执行并阻塞锁的执行,直到其他线程调用release()方法将其设置为解锁状态。
RLock可重入锁是一种常用的线程同步语句。,即使被同一个线程acquire多次也不会被阻塞。【而lock会,这也是二者最大的区别】

在这里插入代码片import threading
lock1 = threading.Lock()
lock1.acquire()
print(lock1)
lock1.acquire()
print(lock1) #无法打印
#-----------------------------------
# lock = threading.RLock()
# lock.acquire()
# print(lock)
# lock.acquire()
# print(lock)#可以打印

Lock与RLock的使用示例

import threading
class SharedCounter:
    _lock=threading.RLock()
    def __init__(self,inital_value=0):
        self._value=inital_value
    def decr(self,delata=1):
        with SharedCounter._lock:
            self._value-=delata
#--------------RLock
class SharedCounter:
    _lock=threading.RLock()
    def __init__(self,inital_value=0):
        self._value=inital_value
    def decr(self,delata=1):
        with SharedCounter._lock:
            self._value-=delata

Event对象

如果程序中的其 他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就会变得非常棘手。为了解决这些问题,我们需要使用threading库中的Event对象。在 初始情况下,Event对象中的信号标志被设置为假。如果有线程等待一个Event对象, 而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行。

from threading import Thread,Event
import time
def countdown(n,started_evt):

    print('开始啦')
    started_evt.set()
    while n>0:
        print("T-minus",n)
        n-=1
        time.sleep(1)
started_evt=Event()
t=Thread(target=countdown,args=(10,started_evt))
t.start()
started_evt.wait()
print('countdown running')

对Event几种方法说明:

  • event.isSet():返回event的状态值;
  • event.wait():如果 event.isSet()==False将阻塞线程;
  • event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;
  • event.clear():恢复event的状态值为False。
Semaphore

Event会唤醒所有等待对象,如果我们编写的程序只希望唤醒一个单独的等待线程,那么最好使用Semaphore
多线程同时运行,能提高程序的运行效率,但是并非线程越多越好,而semaphore信号量可以通过内置计数器来控制同时运行线程的数量,启动线程(消耗信号量)内置计数器会自动减一,线程结束(释放信号量)内置计数器会自动加一;内置计数器为零,启动线程会阻塞,直到有本线程结束或者其他线程结束为止。
semaphore信号量相关函数介绍

  • acquire() 消耗信号量,内置计数器减一;
  • release() 释放信号量,内置计数器加一;
    在semaphore信号量有一个内置计数器,控制线程的数量,acquire()会消耗信号量,计数器会自动减一;release()会释放信号量,计数器会自动加一;当计数器为零时,acquire()调用被阻塞,直到release()释放信号量为止。
import threading
# 导入时间模块
import time
# 添加一个计数器,最大并发线程数量5(最多同时运行5个线程)
semaphore = threading.Semaphore(5)
def foo():
    semaphore.acquire()  # 计数器获得锁
    time.sleep(2)  # 程序休眠2秒
    print("当前时间:", time.ctime())  # 打印当前系统时间
    semaphore.release(
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值