Python实战笔记(三) 多线程 下

简介: Python实战笔记(三) 多线程
+关注继续查看

正文


4、维护线程安全


由于不同线程之间是并行的,如果多个线程同时修改一个数据,那么结果将会是不可预料的

import threading
import time

num = 0

def add(val):
    global num
    time.sleep(1)
    num += val
    print(num)

def main():
    for index in range(1, 9):
        worker = threading.Thread(target = add, args = (index,))
        worker.start()

if __name__ == '__main__':
    main()

程序每次运行的结果都是未知的,所以我们需要采取一些机制,使线程能够按照期望的方式工作


(1)锁对象:threading.Lock 与 threading.RLock


一个 threading.Lock 对象只有两种状态,锁定 (locked) 和非锁定 (unlocked)


任意一个线程可以使用 acquire() 方法将锁对象设置为锁定状态  (称为获得锁)


若此时有其它线程调用 acquire() 方法,那么该线程将会被阻塞


直至其它任意线程使用 release() 方法将锁对象设置为非锁定状态 (称为释放锁)


但如果在调用 release() 方法时,锁对象处于非锁定状态,则会抛出异常

锁对象状态调用的方法结果
unlockedacquire将锁对象设置为锁定状态
lockedacquire阻塞当前线程
lockedrelease将锁对象设置为非锁定状态
unlockedrelease抛出异常


threading.Lock 有两个常用的方法,分别是 acquire() 和 release()


acquire(blocking = True, timeout = -1):获得锁


  • blocking:是否阻塞线程,默认为 True,表示没有获得锁时,将会阻塞当前线程
  • timeout  :最长阻塞时间,默认为 -1    ,表示一直阻塞下去,直至锁被释放


release():释放锁


import threading
import time

num = 0
lock = threading.Lock() # 声明锁对象

def add(val):
    lock.acquire() # 修改数据前,将锁对象设置为锁定状态
    global num
    time.sleep(1)
    num += val
    print(num)
    lock.release() # 修改数据后,将锁对象设置为非锁定状态

def main():
    for index in range(1, 8):
        worker = threading.Thread(target = add, args = (index,))
        worker.start()

if __name__ == '__main__':
    main()

threading.RLock 与 threading.Lock 的功能大致一样,但 threading.RLock 的特别之处在于:


  • 在同一线程内,多次调用 acquire() 方法,不会阻塞线程
  • 使用多少次 acquire() 方法获得锁,就必须使用多少次 release() 方法释放锁
  • 某线程通过 acquire() 方法获得锁,只允许该线程通过 release() 方法释放锁


(2)信号量对象:threading.Semaphore


一个 threading.Semaphore 对象在内部维护一个计数器,规定计数器的值不能小于 0


任意一个线程可以使用 acquire() 方法,使得计数器减 1


如果此时计数器已经为 0,那么将会阻塞当前线程,直至计数器大于 0


任意一个线程可以使用 release() 方法,使得计数器加 1

计数器调用的方法结果
大于 0acquire使计数器减 1
等于 0acquire阻塞当前线程
大于等于 0release使计数器加 1


threading.Semaphore 有两个常用的方法,分别是 acquire() 和 release()


acquire(blocking = True, timeout = -1):使计数器减 1


  • blocking:是否阻塞线程,默认为 True,表示计数器为 0 时,将会阻塞当前线程
  • timeout  :最长阻塞时间,默认为 -1    ,表示一直阻塞下去 ,直至计数器大于 0


release():使计数器加 1


import threading
import time

num = 0
semaphore = threading.Semaphore(1) # 声明信号量,可以指定计数器初始值,默认为 1

def add(val):
    semaphore.acquire() # 使计数器减 1
    global num
    time.sleep(1)
    num += val
    print(num)
    semaphore.release() # 使计数器加 1

def main():
    for index in range(1, 8):
        worker = threading.Thread(target = add, args = (index,))
        worker.start()

if __name__ == '__main__':
    main()

使用信号量还可以使多个线程同时修改一个数据

import threading
import time

semaphore = threading.Semaphore(3)

def run():
    semaphore.acquire()
    time.sleep(1)
    print(threading.current_thread().name, 'Running')
    semaphore.release()
    
def main():
    for _ in range(7):
        worker = threading.Thread(target = run)
        worker.start()

if __name__ == '__main__':
    main()


(3)条件对象:threading.Condition


条件对象在锁对象的基础上封装而成,threading.Condition 常用的方法如下:


acquire():获得锁,调用底层(Lock 或 RLock)所对应的函数


release():释放锁,调用底层(Lock 或 RLock)所对应的函数


wait(timeout = None)

在调用该方法后,调用 release() 释放锁,然后阻塞当前线程,等待其它线程调用 notify() 唤醒

然后在被唤醒后,调用 acquire() 尝试获得锁

若有设置 timeout,即使没有其它线程调用 notify() 唤醒当前线程,也会在超时之后自动被唤醒


wait_for(predicate, timeout = None)​

在调用该方法后,首先调用 predicate,若返回 True  ,则继续执行

若返回 False,调用 release() 释放锁,然后阻塞当前线程,等待其它线程调用 notify() 唤醒

然后在被唤醒后,也会调用 predicate,若返回 False,将会一直阻塞下去

若返回 True  ,调用 acquire() 尝试获得锁

若有设置 timeout,即使没有其它线程调用 notify() 唤醒当前线程,也会在超时之后自动被唤醒


notify(n = 1):唤醒 n 个线程

notify_all()  :唤醒所有线程


import threading
import time

data = 1
condition = threading.Condition()

def isEven():
    global data
    return data % 2 == 0

def wait():
    condition.acquire()        # 要先获得锁,才能释放锁
    print('wait_thread 进入等待')
    condition.wait_for(isEven) # 释放锁,阻塞当前线程,等待唤醒后重新获得锁,继续执行
    print('wait_thread 继续执行')
    condition.release()        # 重新获得锁后,记得要释放锁

def wake():
    global data
    condition.acquire() # 要先获得锁,再修改数据
    data = 2
    print('唤醒 wait_thread')
    condition.notify()
    condition.release() # 获得锁后,要释放锁

def main():
    wait_thread = threading.Thread(target = wait)
    wake_thread = threading.Thread(target = wake)
    wait_thread.start()
    time.sleep(1)
    wake_thread.start()

if __name__ == '__main__':
    main()

# 执行结果
# wait_thread 进入等待
# 唤醒 wait_thread
# wait_thread 继续执行


(4)事件对象:threading.Event


一个 threading.Event 对象在内部会维护一个标记,初始时默认为 False

threading.Event 常用的方法如下:


  • set()      :将标记设置为 True
  • clear()  :将标记设置为 False
  • wait()    :阻塞当前线程,直到标记变为 True
  • is_set():标记是否为 True
import threading
import time

event = threading.Event()

def wait():
    print(threading.current_thread().name, '进入等待')
    event.wait()
    print(threading.current_thread().name, '继续执行')

def wake():
    print('唤醒所有线程')
    event.set()

def main():
    for _ in range(5):
        wait_thread = threading.Thread(target = wait)
        wait_thread.start()
    time.sleep(1)
    wake_thread = threading.Thread(target = wake)
    wake_thread.start()

if __name__ == '__main__':
    main()

# 执行结果
# Thread-1 进入等待
# Thread-2 进入等待
# Thread-3 进入等待
# Thread-4 进入等待
# Thread-5 进入等待
# 唤醒所有线程
# Thread-1 继续执行
# Thread-2 继续执行
# Thread-5 继续执行
# Thread-4 继续执行
# Thread-3 继续执行

文章知识点与官方知识档案匹配,可进一步学习相关知识

目录
相关文章
|
12小时前
|
机器学习/深度学习 自然语言处理 算法
词!自然语言处理之词全解和Python实战!
词!自然语言处理之词全解和Python实战!
3 0
|
1天前
|
JSON 监控 安全
Python日志模块:实战应用与最佳实践
Python日志模块:实战应用与最佳实践
12 0
|
1天前
|
XML 数据格式 Python
Python Django 模版全解与实战
Python Django 模版全解与实战
19 0
Python Django 模版全解与实战
|
11天前
|
Python
Python循环语句实战练习和循环嵌套详解
Python循环语句实战练习和循环嵌套详解
37 0
|
13天前
|
数据采集 数据处理 定位技术
Python空间数据处理实战
这是使用Python进行空间数据处理的系列教程。如果有热心网友对我的该系列博客《Python空间数据处理实战》有什么好的建议的话,比如需要增加哪些内容,写作风格上的改进等,可以直接给我留言或者发邮件,谢谢大家,希望我的分享能帮助到一些热衷GIS与RS数据处理的童鞋!如果有出版社希望,该系列的教程能够出版发行,欢迎联系作者!
37 0
|
18天前
|
Python
Python使用BeautifulSoup4修改网页内容实战
最近有个小项目,需要爬取页面上相应的资源数据后,保存到本地,然后将原始的HTML源文件保存下来,对HTML页面的内容进行修改将某些标签整个给替换掉。对于这类需要对HTML进行操作的需求,最方便的莫过于BeautifulSoup4的库了。
20 0
|
21天前
|
Python
python实战中解决问题的示例
python实战中解决问题的示例
17 1
|
21天前
|
数据采集 人工智能 Java
Python爬虫获取电子书资源实战
最近在学习Python,相对java来说python简单易学、语法简单,工具丰富,开箱即用,适用面广做全栈开发那是极好的,对于小型应用的开发,虽然运行效率慢点,但开发效率极高。大大提高了咱们的生产力。为什么python能够在这几年火起来,自然有他的道理,当然也受益于这几年大数据和AI的火。据说网络上80%的爬虫都是用python写的,不得不说python写爬虫真的是so easy。基本上一个不太复杂的网站可以通过python用100多行代码就能实现你所需要的爬取。
69 1
Python爬虫获取电子书资源实战
|
22天前
|
编解码 运维 监控
Python日志logging实战教程
我们从一个简单的日志记录实战,一步一步实现了自定义日志格式、写日志文件、抽出公共日志模块让其他模块用、同时写多个日志文件并进行日志文件切割、通过配置文件实现日志参数的定义、解决日志中文显示问题。基本覆盖了真实应用场景日志的使用。
30 0
|
22天前
|
数据可视化 Python
推荐文章
更多