Python学习从0到1 day28 Python 高阶技巧 ⑤ 多线程
若事与愿违,请相信,上天自有安排,允许一切如其所是
—— 24.11.12
一、进程、线程
现代操作系统比如Mac OS X,UNIX,Linux,Windows等,都是支持“多任务”的操作系统。
进程
进程:就是一个程序,运行在系统之上,那么便称之这个程序为一个运行进程,并分配进程ID方便系统管理。线程就是程序在系统中运行起来,被系统所管理的一个逻辑概念。
进程就好比一家公司,是操作系统对程序进行运行管理的单位
线程
线程:线程是归属于进程的,一个进程可以开启多个线程,执行不同的工作,是进程实际工作的最小单位。
线程就好比公司的员工,进程可以有多个线程(员工),是进程实际的工作者
操作系统中可以运行多个进程,即多任务运行
一个进程内可以运行多个线程,即多线程运行
注意点:
进程之间是内存隔离的,即不同的进程拥有各自的内存空间。这就类似于不同的公司拥有不同的办公场所。
线程之间是内存共享的,线程是属于进程的,一个进程内的多个线程之间是共享这个进程所拥有的内存空间的。这就好比,公司员工之间是共享公司的办公场所。
二、并行执行
并行执行的意思指的是同一时间做不同的工作。
进程之间就是并行执行的,操作系统可以同时运行好多程序,这些程序都是在并行执行。
除了进程外,线程其实也是可以并行执行的,也就是比如一个Python程序,其实是完全可以做到
一个线程在输出:你好
一个线程在输出:Hello
像这样一个程序在同一时间做两件乃至多件不同的事情,我们就称之为:多线程并行执行
三、threading模块
绝大多数编程语言,都允许多线程编程,Pyhton也不例外。
Python的多线程可以通过threading模块来实现。
语法
thread_obj = threading.Thread([group [, target [, name [, args [, kwargs]]]]])
— self:在实例化对象并调用方法时会自动传入对象本身的引用。在这里表示当前正在被初始化的线程对象实例
— group:暂时无用,未来功能的预留参数
— target:执行的目标任务名
— name:线程名,一般不用设置
— args:以元组的方式给执行任务传参
— kwargs:以字典方式给执行任务传参
— daemon:用于设置线程是否为守护线程。
守护线程是一种在后台运行的线程,当主线程结束时,守护线程通常也会随之结束(即使它自己的任务可能还没有完成)
示例
'''
演示多线程的使用
'''
import time
import threading
def sing():
while True:
print("唱歌ing")
time.sleep(1)
def dance():
while True:
print("跳舞ing")
time.sleep(1)
if __name__ == '__main__':
sing_thread = threading.Thread(target=sing)
dance_thread = threading.Thread(target=dance)
# 启动线程
sing_thread.start()
dance_thread.start()
需要传参的话可以通过:
① args参数通过元组(按参数顺序)的方式传参
② 或使用kwargs参数用字典的形式传参
'''
演示多线程的使用
'''
import time
import threading
def sing(msg):
while True:
print(msg)
time.sleep(1)
def dance(msg):
while True:
print(msg)
time.sleep(1)
if __name__ == '__main__':
# 以元组方式传参
sing_thread = threading.Thread(target=sing,args=("我要唱歌",))
# 以字典方式传参
dance_thread = threading.Thread(target=dance,kwargs={"msg": "我要跳舞"})
# 启动线程
sing_thread.start()
dance_thread.start()
四、多线程编程
1.threading模块的使用
thread_obj = threading.Thread(target=func) 创建线程对象
thread obj.start() 启动线程执行
2.如何传参
① 元组方式
# 以元组方式传参
sing_thread = threading.Thread(target=sing,args=("我要唱
② 字典方式
# 以字典方式传参
dance_thread = threading.Thread(target=dance,kwargs={"msg": "我要跳舞"}
3.线程类 threading
current_thread() 返回与Thread调用方的控制线程相对应的当前对象。
enumerate() 返回Thread当前所有活动对象的列表。
active_count() 返回Thread当前活动的对象数。返回的计数等于所返回列表的长度enumerate()。
main_thread() 返回主要Thread对象。在正常情况下,主线程是启动Python解释器的线程。
get_ident() 返回当前线程的“线程标识符”。这是一个非零整数。它的值没有直接的意义。
4.线程对象 threading.Thread()
守护线程
主线程不管守护线程的执行情况,只要是其他子线程结束且主线程执行完毕,主线程部会关闭。
常见方法
1、setDaemon(True) 可以把子线程设置为主线程的守护线程,此方法必须在start之前
2、join() 让主线程等待子线程执行,此方法在start之后
示例
"""
守护线程:
主线程不管守护线程的执行情况,只要是其他子线程结束且主线程执行完毕,主线程都会关闭。
常见方法:
1、setDaemon(True)方法可以把子线程设置为主线程的守护线程,此方法必须在start之前
2、join()方法,让主线程等待子线程执行,此方法在start之后
"""
import threading
import time
def run1(name, n):
for _ in range(n): # _下划线表示临时变量, 仅用一次,后面无需再用到
print(name, time.ctime())
time.sleep(1)
def run2(name, n):
for _ in range(n):
print(name, time.ctime())
time.sleep(1)
if __name__ == '__main__':
"""
设置子线程t1为守护线程,主线程不等t1运行结束,只要其他的子线程t2运行结果,就会关闭主线程。
"""
t1 = threading.Thread(target=run1, args=("线程1", 10,)) # 注意args参数类型是元组并以“,”结尾
t2 = threading.Thread(target=run2, args=("线程2", 5,))
t1.setDaemon(True) # 设置t1为守护线程
t1.start()
t2.start()
5.线程锁对象
acquire() 锁定 一旦线程获取了锁,随后的尝试将其阻塞,直到释放为止。任何线程都可以释放它。
release() 解锁
互斥锁 threading Lock
递归锁 threading RLock
6.条件对象 threading Condition
threading.condition 是 Python 标准库中用于线程间同步的一个高级机制。它将锁(Lock 或RLock )和条件变量组合在一起,使得线程可以在满足特定条件时等待,并且在条件满足时被唤醒。本质上,它提供了一种更灵活的线程间通信方式,基于共享资源的某种状态(条件)来控制线程的执行。
可以把Condiftion理解为一把高级的锁,它提供了比Lock,RLock更高级的功能,允许我们能够控制复杂的线程同步问题。
主要方法
① acquire 和 release 方法:
acquire 方法用于获取关联的锁。这和单独使用锁的操作类似,当一个线程调用 acquire 时,它会尝试获取锁。如果锁已经被其他线程持有,那么这个线程会被阻塞,直到锁被释放。
release 方法用于释放关联的锁,在完成对共享资源的操作后,线程应该释放锁,以便其他线程可以获取锁并访问共享资源。例如:
import threading
cond = threading.Condition()
cond.acquire()
try:
# 在这里访问共享资源
finally:
cond.release()
② wait方法:
线程调用 wait 方法时,会释放当前持有的锁,并进入等待状态。这个线程会一直等待,直到被其他线程通过 notify 或 notify_all 方法唤醒。被唤醒后,线程会重新尝试获取锁,只有获取到锁后才能继续执行后续代码。
import threading
buffer = []
cond = threading.Condition()
def consumer():
with cond:
while not buffer:
print("消费者:缓冲区为空,等待数据...")
cond.wait()
data = buffer.pop(0)
print("消费者:消费了数据", data)
③ notify 和 notify_all 方法:
notify方法用于唤醒一个正在等待该条件变量的线程。在共享资源的状态发生改变,可能使得等待的线程能够继续执行时,应该调用这个方法来唤醒一个等待的线程。
notify_all方法则会唤醒所有正在等待该条件变量的线程。不过,只有在这些被唤醒的线程成功获取到锁后,才能真正继续执行。例如,生产者生产了新的数据后,可以调用 notify 方法唤醒等待的消费者线程:
import threading
buffer = []
cond = threading.Condition()
def producer():
with cond:
data = 1 # 假设生产的数据为1
buffer.append(data)
print("生产者:生产了数据", data)
cond.notify()
7.信号量对象 threading Semaphole
Semaphore() 内部计数器
BoundedSemaphore() 继承Semaphore类,设定信号量边界值
"""
信号量(BoundedSemaphore类):
信号量通常用于保护容量有限的资源
信号量管理一个内部计数器,该内部计数器随每个acquire()调用而递减,并随每个 调用而递增release()。
计数器永远不能低于零。当acquire() 发现它为零时,它将阻塞,直到其他线程调用为止 release()。
"""
import threading, time
# 设置信号量的边界值
semaphore = threading.BoundedSemaphore(value=3)
def run():
semaphore.acquire() # 加锁
print(threading.current_thread().getName(), time.ctime())
time.sleep(5)
semaphore.release() # 释放
if __name__ == '__main__':
for _ in range(10):
t = threading.Thread(target=run)
t.start()
# 返回Thread当前所有活动对象的列表。通过运行结果我们可以看出系统是先一次性创建10个线程,然后根据信号量边界值3,一次性运行3个线程,其他线程等待锁。
print(threading.enumerate())
"""
python多线程的缺陷:
1、GIL-全局解释器锁
2、无论系统CPU是几核的,只能使用一个来处理进程
"""
8.事件对象 threading Event
set() 将事件对象的内部标志设置为 True 。这意味着等待这个事件的线程将会被唤醒并继续执行
import threading
event = threading.Event()
# 其他线程在等待这个事件
event.set()
clear() 将事件对象的内部标志设置为 False 。如果在 clear 之后有线程调用 wait 方法,这些线程将会被阻塞,直到事件再次被设置。例如:
import threading
event = threading.Event()
event.set()
# 现在清除事件
event.clear()
wait(timeout=None) 如果“Flag"值为 False,那么当程序执行event.wait方法时就会阻塞;如果“Flag"值为True,那么执行event.wait方法时便不再阻塞
import threading
event = threading.Event()
def worker():
print("线程开始等待事件")
event.wait()
print("事件已触发,线程继续执行")
t = threading.Thread(target=worker)
t.start()
# 在这里可以进行一些其他操作,然后设置事件
event.set()
is set() 这是一个只读属性,用于检查事件对象的内部标志是否为 true 。可以用于在不阻塞线程的情况下检查事件的状态。例如:
import threading
event = threading.Event()
if not event.is_set():
print("事件尚未设置")
event.set()
if event.is_set():
print("事件已设置")
"""
事件对象 threading.Event
set() 将“Flag”设置为True
clear() 将“Flag”设置为False
wait() 如果“Flag”值为 False,主线程就会阻塞;如果“Flag”值为True,主线程不再阻塞。
isSet() 判断“Flag”值是否为True。
"""
import threading
import time
#实例化一个事件对象
event = threading.Event()
def run():
while not event.isSet():
print(threading.current_thread().getName(), time.ctime())
time.sleep(5)
event.wait(timeout=10) # 阻塞线程,为什么timeout=10没起到作用
if __name__ == '__main__':
#使用多线程去调用run
for i in range(10):
th = threading.Thread(target=run)
th.start()
#阻塞30s后运行主线程
time.sleep(30)
event.set()
9.Timer计时器对象 threading Timer
参数:(self, interval, function, args=None, kwargs=None)
start() 启动计时器
cancel() 停止计时器(在其动作开始之前)
"""
Timer对象
"""
import threading
# 实例化一个计时器
timer = threading.Timer
def hello():
print("hello, world")
# 10s后执行hello
print("开始执行")
t = timer(10, hello)
t.cancel()
t.start()