Python开发—— 多线程开发

1. 了解线程

1.1 进程和线程

        进程是资源分配的最小单位。每个进程都有独立的地址空间,进程之间互不影响。操作系统会为每个进程分配独立的内存空间和其他资源,如文件描述符等。

        线程是CPU调度的最小单位。一个进程可以包含多个线程,这些线程共享该进程的资源(如内存),但每个线程都有自己的栈空间和程序计数器。线程之间切换开销较小,适合并发执行。

2. Thread:执行线程的对象

2.1 threading.Thread构造函数

        构造一个新的线程对象,其构造函数的参数如下:

        group: 目前始终为 `None`,用于将来实现线程组时使用。

        target: 指定线程启动时需要调用的可调用对象(函数或方法)。如果为 `None`,则什么也不调用。常用于指定线程要执行的任务。

        name: 指定线程的名称。如果为 `None`,则系统自动分配一个以 "Thread-N" 开头的名字。便于调试和管理。

        args: 传递给 `target` 函数的位置参数元组。用于传递参数给线程执行的函数。

        kwargs: 传递给 `target` 函数的关键字参数字典。用于传递参数给线程执行的函数。

        daemon: 指定线程是否为守护线程。守护线程会在主线程结束后自动结束,非守护线程则会阻止主线程的结束。守护线程常用于后台运行的任务,如日志记录器。 示例代码

import threading

def print_numbers(n):
    for i in range(n):
        print(f"Number: {i}")

# 创建线程
t = threading.Thread(target=print_numbers, args=(5,), name="NumberPrinter")
t.start()
t.join()
print("Thread has finished execution")

2.2 join()方法等待线程结束

        join() 方法用于阻塞当前线程,直到调用 `join()` 的线程执行完毕。常用于在主线程中等待其他子线程结束后再继续执行。 示例代码

import threading
import time

def delayed_print():
    time.sleep(2)
    print("Delayed print")

# 创建并启动线程
t = threading.Thread(target=delayed_print)
t.start()

print("Waiting for the thread to finish...")
t.join()
print("Thread has finished execution")

3. 线程同步

3.1 线程同步的概念

        线程同步用于协调多个线程对共享资源的访问,避免数据竞争和不一致性。当多个线程同时访问和修改共享资源时,可能会导致数据不一致,线程同步机制可以确保同一时间只有一个线程能够访问共享资源,从而保证数据的一致性。

3.2 锁的概念

        锁(Lock)是一种用于线程同步的原语,用于确保同一时间只有一个线程能访问某个特定资源。

4. Lock 和 RLock 对象

4.1 Lock:锁原语对象

        Lock` 是最简单的一种线程同步机制。它只有两个状态:locked 和 unlocked。当一个线程获取到锁时,锁的状态变为 locked,其他线程尝试获取该锁时会被阻塞,直到该锁被释放。

import threading

lock = threading.Lock()
shared_resource = 0

def increment():
    global shared_resource
    with lock:
        local_copy = shared_resource
        local_copy += 1
        shared_resource = local_copy
        print(f"Resource value: {shared_resource}")

threads = []
for i in range(5):
    t = threading.Thread(target=increment)
    threads.append(t)
    t.start()

for t in threads:
    t.join()

4.2 RLock:可重入锁对象

        RLock(可重入锁)允许同一个线程多次获取同一个锁,每次获取锁后必须释放相同次数的锁。这样可以避免在同一线程内多次获取锁时发生死锁。 

import threading

rlock = threading.RLock()
shared_resource = 0

def increment():
    global shared_resource
    with rlock:
        with rlock:
            local_copy = shared_resource
            local_copy += 1
            shared_resource = local_copy
            print(f"Resource value: {shared_resource}")

threads = []
for i in range(5):
    t = threading.Thread(target=increment)
    threads.append(t)
    t.start()

for t in threads:
    t.join()

4.3 Lock 和 RLock 的区别

        Lock: 一个线程如果多次获取同一个 `Lock`,会导致死锁。适用于简单的线程同步情况。

        RLock: 允许同一个线程多次获取锁,必须释放相同次数的锁。适用于复杂的线程同步情况,需要在同一线程内多次获取锁。

5. 死锁

5.1 死锁是什么

        死锁是一种情况,当两个或多个线程互相等待对方释放资源时,会导致所有线程都被无限期地阻塞。简单来说,死锁是线程在等待中无法继续执行的状态。 

import threading

lock1 = threading.Lock()
lock2 = threading.Lock()

def task1():
    with lock1:
        print("Task 1 acquired lock 1")
        with lock2:
            print("Task 1 acquired lock 2")

def task2():
    with lock2:
        print("Task 2 acquired lock 2")
        with lock1:
            print("Task 2 acquired lock 1")

thread1 = threading.Thread(target=task1)
thread2 = threading.Thread(target=task2)

thread1.start()
thread2.start()

thread1.join()
thread2.join()

        这个示例中,如果task1拿到lock1后等待 lock2,同时 task2 拿到 lock2 后等待 lock1,就会发生死锁。

6. 线程的其他同步机制

        除了 `Lock` 和 `RLock`,Python `threading` 模块还提供了其他一些同步机制:

6.1 Semaphore:信号量

        信号量用于控制对共享资源的访问数量。它维护一个计数器,每次 `acquire` 时减少计数器,每次 `release` 时增加计数器。计数器不能小于零,当计数器为零时,试图 `acquire` 的线程会被阻塞。 示例代码

import threading
import time

semaphore = threading.Semaphore(3)

def access_resource():
    with semaphore:
        print(f"Resource accessed by {threading.current_thread().name}")
        time.sleep(1)

threads = []
for i in range(10):
    t = threading.Thread(target=access_resource, name=f"Thread-{i+1}")
    threads.append(t)
    t.start()

for t in threads:
    t.join()

6.2 Event:事件对象

        事件对象用于线程间通信。一个线程等待事件发生,另一个线程触发事件。事件对象有一个内部标志,初始值为 `False`。当调用 `set()` 方法时,内部标志变为 `True`,所有等待该事件的线程被唤醒。当调用 `clear()` 方法时,内部标志变为 `False`。 

import threading
import time

event = threading.Event()

def wait_for_event():
    print(f"{threading.current_thread().name} waiting for event")
    event.wait()
    print(f"{threading.current_thread().name} event triggered")

def trigger_event():
    time.sleep(2)
    print("Event will be triggered")
    event.set()

threads = [threading.Thread(target=wait_for_event, name=f"Thread-{i+1}") for i in range(5)]
for t in threads:
    t.start()

trigger_thread = threading.Thread(target=trigger_event)
trigger_thread.start()

for t in threads:
    t.join()
trigger_thread.join()

6.3 Condition:条件变量

        条件变量允许线程在满足特定条件时被唤醒。它通常与锁(Lock 或 RLock)一起使用,用于复杂的线程间同步问题。 

import threading
import time

condition = threading.Condition()
shared_resource = []

def producer():
    global shared_resource
    while True:
        with condition:
            if len(shared_resource) < 5:
                item = len(shared_resource) + 1
                shared_resource.append(item)
                print(f"Produced item {item}")
                condition.notify_all()
            time.sleep(1)

def consumer():
    global shared_resource
    while True:
        with condition:
            while not shared_resource:
                condition.wait()
            item = shared_resource.pop(0)


            print(f"Consumed item {item}")
        time.sleep(1)

producer_thread = threading.Thread(target=producer)
consumer_thread = threading.Thread(target=consumer)

producer_thread.start()
consumer_thread.start()

producer_thread.join()
consumer_thread.join()

        这些同步机制可以帮助你更好地管理多线程程序中的资源访问,确保数据一致性和线程安全。

猜你喜欢

转载自blog.csdn.net/weixin_65190179/article/details/139452767