并发编程

串行,并行与并发

串行:在一个CPU上,按顺序完成多个任务 并行:当任务数小于等于CPU核数时,这些任务是一起执行的 并发:一般任务数多于CPU核数,再一个CPU上,交替的处理多个任务,它是通过操作系统的各种任务调度算法,实现用多个任务"一起"执行

进程、线程和协程

进程

进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。现代操作系统都是支持多任务的,简单地说,就是操作系统可以同时运行多个任务。打个比方,你一边在用逛淘宝,一边在听音乐,一边在用微信聊天,这就是多任务,至少同时有3个3任务正在运行。对于操作系统来说,一个任务就是一个进程(Process)。

线程

线程(Thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。有些进程还不止同时干一件事,比如微信,它可以同时进行打字聊天,视频聊天,朋友圈等事情。在一个进程内部,要同时干多件事,就需要同时运行多个"子任务",我们把进程内的这些 "子任务"称为线程(Thread)。

线程的创建

Python的标准库提供了两个模块: _ thread 和 threading , _ thread是低级模块, threading是高级模块,对 _ thread 进行了封装。线程可以通过两种方式创建:

  1. 方法包装
python 复制代码
def func1(name):  
    for i in range(3):  
    print(f"thread:{name}:{i}")  
    sleep(1)  
  
  
if __name__ == '__main__':  
    print("主线程start...")  
    t1 = Thread(target=func1,args=("t1",))  
    t2 = Thread(target=func1,args=("t2",))  

    t1.start()  
    t2.start()  
    print("主线程end...")
    
"""
主线程start...
thread:t1:0
thread:t2:0
主线程end...
thread:t2:1thread:t1:1

thread:t1:2
thread:t2:2

"""
  1. 类包装
python 复制代码
class MyThread(Thread):  
    def __init__(self,name):  
    Thread.__init__(self)  
    self.name = name  
  
    def run(self):  
        for i in range(3):  
            print(f"thread:{self.name}:{i}")  
            sleep(1)  
  
if __name__ =="__main__":  
    print("主线程start...")  
    t1 = MyThread("t1")  
    t2 = MyThread("t2")  
  
    t1.start()  
    t2.start()  
    print("主线程end...")
    
"""
主线程start...
thread:t1:0
thread:t2:0主线程end...

thread:t2:1
thread:t1:1
thread:t1:2thread:t2:2
"""

join()

从上面的结果来看,主线程不会等待子线程结束,而是执行完毕之后,主线程直接关闭,如果想要主线程等待某一个线程结束之后,主线程再结束,就可以使用join方法

python 复制代码
class MyThread(Thread):  
    def __init__(self, name):  
        Thread.__init__(self)  
        self.name = name  
  
    def run(self):  
        for i in range(3):  
            print(f"thread:{self.name}:{i}")  
            sleep(1)  
  
if __name__ == "__main__":  
    print("主线程start...")  
    t1 = MyThread("t1")  
    t2 = MyThread("t2")  
    t1.start()  
    t2.start()  
    t1.join()  
    # t2.join()  
    print("主线程end...")
    
"""
主线程start...
thread:t1:0
thread:t2:0
thread:t2:1thread:t1:1

thread:t1:2thread:t2:2

主线程end...
"""

守护线程

当主线程执行结束后,其守护线程不管有没有执行结束,都会立刻结束

python 复制代码
class MyThread(Thread):  
  
    def __init__(self, name):  
        Thread.__init__(self)  
        self.name = name  
  
    def run(self):  
        for i in range(3):  
            print(f"thread:{self.name}:{i}")  
            sleep(1)  
  
if __name__ == "__main__":  
    print("主线程start...")  
    t1 = MyThread("t1")  
    # t1设置为守护线程
    t1.daemon = True  
  
    t1.start()  
    print("主线程end...")
    
"""
主线程start...
thread:t1:0
主线程end...

Process finished with exit code 0
"""

线程同步和互斥锁

处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象。这时候,我们就需要用到"线程同步"。线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕后,下一个线程再使用。

未使用线程同步的情况:

python 复制代码
class Account:  
def __init__(self, money, name):  
self.money = money  
self.name = name  
  
class Drawing(Thread):  
  
def __init__(self,drawingNum,account):  
Thread.__init__(self)  
self.drawingNum = drawingNum  
self.account = account  
self.expenseTotal = 0  
  
def run(self):  
if self.account.money < self.drawingNum:  
return  
sleep(1)  
self.account.money -= self.drawingNum  
self.expenseTotal += self.drawingNum  
print(f"账户:{self.account.name},余额是:{self.account.money}")  
print(f"账户:{self.account.name},总共取了:{self.expenseTotal}")  
  
if __name__ == "__main__":  
a1 = Account(100,"hhc")  
draw1 = Drawing(80,a1)  
draw2 = Drawing(80,a1)  
draw1.start()  
draw2.start()  
  
"""  
账户:hhc,余额是:20账户:hhc,余额是:-60  
账户:hhc,总共取了:80  
  
账户:hhc,总共取了:80  
"""

没有线程同步机制,两个线程同时操作同一个账户对象,从只有100元的账户,轻松取出160元,账户余额竟然成为了-60

通过互斥锁实现线程同步:

互斥锁: 对共享数据进行锁定,保证同一时刻只能有一个线程去操作。多个线程一起去抢,抢到锁的线程先执行,没有抢到 锁的线程需要等待,等互斥锁使用完释放后,其它等待的线程再去抢这个锁。 threading模块中定义了Lock变量,这个变量本质上是一个函数,通过调用这个函数可以获取一把互斥锁

python 复制代码
class Account:  
    def __init__(self, money, name):  
        self.money = money  
        self.name = name  

class Drawing(Thread):  
  
    def __init__(self,drawingNum,account):  
        Thread.__init__(self)  
        self.drawingNum = drawingNum  
        self.account = account  
        self.expenseTotal = 0  
  
    def run(self):  
        lock1.acquire()  
        if self.account.money < self.drawingNum:  
            print("余额不足")  
            return  
        sleep(1)  
        self.account.money -= self.drawingNum  
        self.expenseTotal += self.drawingNum  
        lock1.release()  
        print(f"账户:{self.account.name},余额是:{self.account.money}")  
        print(f"账户:{self.account.name},总共取了:{self.expenseTotal}")  
  
if __name__ == "__main__":  
    a1 = Account(100,"hhc")  
    lock1 = Lock()  
    draw1 = Drawing(80,a1)  
    draw2 = Drawing(80,a1)  

    draw1.start()  
    draw2.start()  
  
"""  
账户:hhc,余额是:20  
账户:hhc,总共取了:80  
余额不足  
"""

死锁

在多线程程序中,死锁问题很大一部分是由于一个线程同时获取多个锁造成的

python 复制代码
def fun1():  
    lock1.acquire()  
    print("fun1拿到菜刀")  
    sleep(2)  
    lock2.acquire()  
    print("fun2拿到锅")  
    lock2.release()  
    print("fun2释放锅")  
    lock1.release()  
    print("fun2释放菜刀")  
def fun2():  
    lock2.acquire()  
    print('fun2拿到锅')  
    lock1.acquire()  
    print('fun2拿到菜刀')  
    lock1.release()  
    print('fun2释放菜刀')  
    lock2.release()  
    print('fun2释放锅')  
  
if __name__ == '__main__':  
    lock1 = Lock()  
    lock2 = Lock()  

    t1 = Thread(target=fun1)  
    t2 = Thread(target=fun2)  
    t1.start()  
    t2.start()

死锁是由于"同步块需要同时持有多个锁造成"的,要解决这个问题,思路很简单,就是同一个代码块,不要同时持有两个对象锁。

信号量

互斥锁使用后,一个资源同时只有一个线程访问。

信号量:一个资源同一时间可以多个线程进行访问。

信号量底层就是一个内置的计数器。每当资源获取时(调用acquire)计数器-1,资源释放时(调用release)计数器+1

python 复制代码
from threading import Thread, Lock  
from time import sleep  
from multiprocessing import Semaphore  
  
# 信号量  
"""  
一个房间一次只允许两个人通过  
若不使用信号量,会造成所有人都进入这个房子  
若只允许一人通过可以用锁-Lock()  
"""  
def home(name, se):  
    se.acquire()  
    print(f"{name}进入了房间")  
    sleep(3)  
    print((f'******************{name}走出来房间'))  
    se.release()  
  
if __name__ == "__main__":  
    # 同时只允许两个线程进入  
    se = Semaphore(2)  
    for i in range(7):  
        p = Thread(target=home, args=(f"tom{i}", se))  
        p.start()  
  
"""  
tom0进入了房间  
tom1进入了房间  
******************tom0走出来房间  
******************tom1走出来房间  
tom3进入了房间tom2进入了房间  
  
******************tom3走出来房间  
******************tom2走出来房间  
tom4进入了房间  
tom5进入了房间  
******************tom5走出来房间******************tom4走出来房间  
  
tom6进入了房间  
******************tom6走出来房间  
  
Process finished with exit code 0  
"""

协程

协程是一种在线程中,比线程 更加轻量级的存在,由程序员自己写程序来管理。当出现IO阻塞时,CPU一直等待IO返回,处于空转状态。这时候用 协程,可以执行其他任务。当IO返回结果后,再回来处理数据。充分利用了IO等待的时间,提高了效率。

同步和异步

同步和异步强调的是消息通信机制

同步:A调用B,等待B返回结果后,A继续执行

异步:A调用B,A继续执行,不等待B返回结果;B 有结果了,通知A,A再做处理

相关推荐
Samuel-Gyx35 分钟前
2025第十六届蓝桥杯python B组满分题解(详细)
python·职场和发展·蓝桥杯
行者无疆xcc1 小时前
【Django】设置让局域网内的人访问
后端·python·django
患得患失9491 小时前
【后端】【python】Python 爬虫常用的框架解析
开发语言·爬虫·python
愚公搬代码3 小时前
【愚公系列】《Python网络爬虫从入门到精通》058-自定义分布式爬取诗词排行榜数据
分布式·爬虫·python
不会飞的鲨鱼3 小时前
【某比特币网址请求头部sign签名】RSA加密逆向分析
javascript·爬虫·python
令狐少侠20113 小时前
AI之pdf解析:Tesseract、PaddleOCR、RapidPaddle(可能为 RapidOCR)和 plumberpdf 的对比分析及使用建议
人工智能·python·pdf
世界的尽头在哪里3 小时前
python测试框架之pytest
开发语言·python·测试工具·单元测试·pytest
_一条咸鱼_3 小时前
Python 语法之注释详解(二)
人工智能·python·面试
小洛~·~4 小时前
《深度学习入门:基于Python的理论与实现》第四章神经网络的学习—上篇(损失函数登场)
python·深度学习·神经网络
bjxiaxueliang4 小时前
一文详解opencv-python环境搭建:Mac配置python的cv2开发环境
python·opencv·macos