Python学习从0到1 day28 Python 高阶技巧 ⑤ 多线程

若事与愿违,请相信,上天自有安排,允许一切如其所是

------ 24.11.12

一、进程、线程

现代操作系统比如Mac OS X,UNIX,Linux,Windows等,都是支持"多任务"的操作系统。

进程

进程:就是一个程序,运行在系统之上,那么便称之这个程序为一个运行进程,并分配进程ID方便系统管理。线程就是程序在系统中运行起来,被系统所管理的一个逻辑概念。

进程就好比一家公司,是操作系统对程序进行运行管理的单位


线程

线程:线程是归属于进程的,一个进程可以开启多个线程,执行不同的工作,是进程实际工作的最小单位。

线程就好比公司的员工,进程可以有多个线程(员工),是进程实际的工作者

操作系统中可以运行多个进程,即多任务运行

一个进程内可以运行多个线程,即多线程运行


注意点:

进程之间是内存隔离的,即不同的进程拥有各自的内存空间。这就类似于不同的公司拥有不同的办公场所。

线程之间是内存共享的,线程是属于进程的,一个进程内的多个线程之间是共享这个进程所拥有的内存空间的。这就好比,公司员工之间是共享公司的办公场所。


二、并行执行

并行执行的意思指的是同一时间做不同的工作。

进程之间就是并行执行的,操作系统可以同时运行好多程序,这些程序都是在并行执行。

除了进程外,线程其实也是可以并行执行的,也就是比如一个Python程序,其实是完全可以做到

一个线程在输出:你好

一个线程在输出:Hello

像这样一个程序在同一时间做两件乃至多件不同的事情,我们就称之为:多线程并行执行


三、threading模块

绝大多数编程语言,都允许多线程编程,Pyhton也不例外。

Python的多线程可以通过threading模块来实现。

语法

python 复制代码
thread_obj = threading.Thread([group [, target [, name [, args [,  kwargs]]]]])

--- self:在实例化对象并调用方法时会自动传入对象本身的引用。在这里表示当前正在被初始化的线程对象实例

--- group:暂时无用,未来功能的预留参数

--- target:执行的目标任务名

--- name:线程名,一般不用设置

--- args:以元组的方式给执行任务传参

--- kwargs:以字典方式给执行任务传参

--- daemon:用于设置线程是否为守护线程。

守护线程是一种在后台运行的线程,当主线程结束时,守护线程通常也会随之结束(即使它自己的任务可能还没有完成)


示例

python 复制代码
'''
演示多线程的使用
'''
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参数用字典的形式传参

python 复制代码
'''
演示多线程的使用
'''
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.如何传参

① 元组方式

python 复制代码
    # 以元组方式传参
    sing_thread = threading.Thread(target=sing,args=("我要唱

② 字典方式

python 复制代码
    # 以字典方式传参
    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之后

示例

python 复制代码
"""
守护线程:
主线程不管守护线程的执行情况,只要是其他子线程结束且主线程执行完毕,主线程都会关闭。
常见方法:
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 方法用于释放关联的锁,在完成对共享资源的操作后,线程应该释放锁,以便其他线程可以获取锁并访问共享资源。例如:

python 复制代码
import threading
cond = threading.Condition()
cond.acquire()
try:
    # 在这里访问共享资源
finally:
    cond.release()
② wait方法:

线程调用 wait 方法时,会释放当前持有的锁,并进入等待状态。这个线程会一直等待,直到被其他线程通过 notify 或 notify_all 方法唤醒。被唤醒后,线程会重新尝试获取锁,只有获取到锁后才能继续执行后续代码。

python 复制代码
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 方法唤醒等待的消费者线程:

python 复制代码
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类,设定信号量边界值

python 复制代码
"""
信号量(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 。这意味着等待这个事件的线程将会被唤醒并继续执行

python 复制代码
import threading
event = threading.Event()
# 其他线程在等待这个事件
event.set()

clear() 将事件对象的内部标志设置为 False 。如果在 clear 之后有线程调用 wait 方法,这些线程将会被阻塞,直到事件再次被设置。例如:

python 复制代码
import threading
event = threading.Event()
event.set()
# 现在清除事件
event.clear()

wait(timeout=None) 如果"Flag"值为 False,那么当程序执行event.wait方法时就会阻塞;如果"Flag"值为True,那么执行event.wait方法时便不再阻塞

python 复制代码
import threading
event = threading.Event()
def worker():
    print("线程开始等待事件")
    event.wait()
    print("事件已触发,线程继续执行")
t = threading.Thread(target=worker)
t.start()
# 在这里可以进行一些其他操作,然后设置事件
event.set()

is set() 这是一个只读属性,用于检查事件对象的内部标志是否为 true 。可以用于在不阻塞线程的情况下检查事件的状态。例如:

python 复制代码
import threading
event = threading.Event()
if not event.is_set():
    print("事件尚未设置")
event.set()
if event.is_set():
    print("事件已设置")
python 复制代码
"""
事件对象 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() 停止计时器(在其动作开始之前)

python 复制代码
"""
Timer对象
"""
import threading

# 实例化一个计时器
timer = threading.Timer


def hello():
    print("hello, world")


# 10s后执行hello
print("开始执行")
t = timer(10, hello)
t.cancel()
t.start()
相关推荐
阿阳微客4 小时前
Steam 搬砖项目深度拆解:从抵触到真香的转型之路
前端·笔记·学习·游戏
Chef_Chen9 小时前
从0开始学习R语言--Day18--分类变量关联性检验
学习
键盘敲没电9 小时前
【IOS】GCD学习
学习·ios·objective-c·xcode
海的诗篇_9 小时前
前端开发面试题总结-JavaScript篇(一)
开发语言·前端·javascript·学习·面试
AgilityBaby10 小时前
UE5 2D角色PaperZD插件动画状态机学习笔记
笔记·学习·ue5
AgilityBaby10 小时前
UE5 创建2D角色帧动画学习笔记
笔记·学习·ue5
武昌库里写JAVA11 小时前
iview Switch Tabs TabPane 使用提示Maximum call stack size exceeded堆栈溢出
java·开发语言·spring boot·学习·课程设计
一弓虽12 小时前
git 学习
git·学习
Moonnnn.14 小时前
【单片机期末】串行口循环缓冲区发送
笔记·单片机·嵌入式硬件·学习
viperrrrrrrrrr715 小时前
大数据学习(131)-Hive数据分析函数总结
大数据·hive·学习