Day10-3【Python 进程和线程】Python多线程编程:锁住你的心,也锁住你的数据

说到多线程,你的脑海是不是马上浮现出一群线程在CPU上快速跳跃,像小马奔腾一样,处理多个任务?嗯,虽然它们在奔腾,但你得小心,它们的“马路”上有时候会发生一些“车祸”!特别是在Python中,掌控线程可不是简单的事情。

 

7c0e01040b0b224aeaef38526f6eabab.jpeg

 

一、线程

1. 线程的基本概念——多线程:这不是开玩笑

多任务处理(Multi-tasking)到底是怎么回事呢?它可以通过多个进程来实现,也可以通过一个进程内的多个线程来完成。你可能会觉得,多进程比较牛,但实际上,多线程在现代编程中才是扛把子,尤其在资源共享和高效任务处理上。

在Python中,线程不是你想象中的“伪线程”,而是真正的POSIX线程。这是什么意思?简单来说,Python线程是由操作系统直接支持的执行单位,它不是“模拟出来的”,而是真实的“多任务大军”。

2. Python的线程模块——_thread VS threading

Python有两个模块可以用来玩转多线程——_threadthreading。其中,_thread是底层的,稍微有些“野性”,而threading则是高级模块,提供了更多的封装和便利。大多数情况下,你只需要使用threading模块,它的简洁与稳定让它成为了大家的最爱。

3. 启动一个线程——来,开个线程试试!

创建并启动一个线程其实比你想象的要简单。下面是一个简单的例子,展示了如何用threading模块启动一个线程:

pythonimport time, threading
def loop():    print('thread %s is running...' % threading.current_thread().name)    n = 0    while n < 5:        n += 1        print(f'thread {threading.current_thread().name} >>> {n}')        time.sleep(1)    print('thread %s ended.' % threading.current_thread().name)
# 主线程开始print('thread %s is running...' % threading.current_thread().name)t = threading.Thread(target=loop, name='LoopThread')t.start()t.join()  # 等待线程t结束print('thread %s ended.' % threading.current_thread().name)

 

输出结果:

arduinothread MainThread is running...thread LoopThread is running...thread LoopThread >>> 1thread LoopThread >>> 2thread LoopThread >>> 3thread LoopThread >>> 4thread LoopThread >>> 5thread LoopThread ended.thread MainThread ended.

 

二、 线程共享资源——当多个线程争抢一个“蛋糕”

然而,真正的麻烦来了:共享资源。在多线程中,所有线程共享相同的内存空间,这就意味着,多个线程同时修改同一变量时,可能会发生数据混乱。让我们用一个“银行存款”的例子来演示:​​​​​​​

pythonimport time, threading


# 假定这是你的银行存款:​​​​​​​

balance = 0
def change_it(n):    global balance    balance = balance + n    balance = balance - n
def run_thread(n):    for i in range(10000000):        change_it(n)
t1 = threading.Thread(target=run_thread, args=(5,))t2 = threading.Thread(target=run_thread, args=(8,))t1.start()t2.start()t1.join()t2.join()print(balance)

问题出现了:我们定义了一个共享变量balance,并启动了两个线程,理论上balance应该始终为0。但由于线程执行的时机问题,最终的结果可能会是负数。

为什么会发生这种情况?

简单地说,balance = balance + n这段代码看似简单,但它在CPU执行时需要两步:计算并存储临时变量,然后再赋值。多个线程同时操作时,它们的“存取”顺序可能会乱套,最终导致存款余额计算出错!

1. 解决办法——锁(Lock)

既然多线程容易出错,那我们该怎么办呢?上锁!给共享资源加把锁,确保同一时刻只有一个线程可以访问它,其他线程就得乖乖地等着。这样,我们就能避免数据混乱的问题了。​​​

pythonbalance = 0lock = threading.Lock()
def run_thread(n):    for i in range(100000):        lock.acquire()  # 获取锁        try:            change_it(n)        finally:            lock.release()  # 释放锁

 

注意:上锁的好处是保证了数据安全,但代价也是显而易见的。锁住的部分代码无法并发执行,效率降低。如果多个线程频繁获取和释放锁,可能会导致性能瓶颈

2. 死锁——锁住你的线程,直到死

锁虽然能解决共享数据的问题,但也容易带来一个隐患——死锁。死锁发生在两个或多个线程互相持有对方需要的锁时,导致它们都在等待对方释放锁,最终无法执行下去。

例如,线程A持有锁1,等待锁2;线程B持有锁2,等待锁1。如果线程A和线程B都没有释放各自的锁,就会导致死锁。

3. GIL——Python的“锁”问题

有些时候,你可能会想到:“如果我的机器有多个CPU核心,难道就不能同时运行多个线程了吗?”答案是,不行,至少在Python中是这样的。Python的全局解释器锁(GIL)使得同一时刻只有一个线程能执行Python字节码。即使你有多核CPU,也只能有一个线程在执行,其他线程只能排队等候。

这意味着,Python的多线程并不能充分利用多核CPU,除非你采用多进程或者其他并发模型,比如使用C扩展来绕过GIL。

* 小结:Python的多线程,是一个“美丽的梦”

多线程编程虽然能带来并发的优势,但也伴随着锁、死锁等问题。如果你想在Python中充分利用多核CPU,多线程可能不太靠谱,多进程才是王道。

总之,Python的多线程是一个充满挑战和乐趣的领域。如果你小心驾驶,确保使用锁来保证数据安全,那你就能驾驭这匹“多线程战马”。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值