并发编程(进程、线程、死锁)

1. 基本概念

进程(Process)
进程是一个程序的实例,是操作系统分配资源和调度的基本单位。每个进程都有独立的内存空间。适合需要强隔离的任务。
线程(Thread)
线程是进程中的一个执行单元,是CPU调度的基本单位。一个进程可以包含多个线程,这些线程共享进程的内存空间。适合需要轻量级并发的任务。
协程(Coroutine)
协程是一种更加轻量级的线程,可以在一个线程内实现并发。协程由程序员手动调度,而非操作系统。适合I/O密集型任务和需要大量并发的场景。
并发
在同一时间段内,多个任务(共享资源)在一个或多个处理器上交替进行执行。并发系统通过分时复用(time-sharing)使多个任务看似同时进行,虽然在任意时刻,单个处理器只能执行一个任务。
并行
在同一时间点上,多个任务(独立执行)在多个处理器上同时进行执行的能力。并行处理需要硬件支持(多核处理器或多处理器系统)。
多线程:多线程允许程序并发执行多个任务,适用于需要处理多个独立任务的场景。

2. 进程之间通信与同步

通信:主要解决进程之间的数据交换问题。
同步:主要解决进程之间的执行顺序和资源访问协调问题。

进程之间通信

进程之间通信是指多个进程之间交换数据的机制。这种机制允许进程彼此发送和接收信息,以便协调工作或共享数据。常见的进程间通信方式包括:

1. 管道(Pipe)

管道是一种半双工的通信方式,数据只能单向流动。通常用于具有亲缘关系的进程之间的通信(如父子进程)。

import os

r, w = os.pipe()

pid = os.fork()
if pid > 0:
    # Parent process
    os.close(r)
    w = os.fdopen(w, 'w')
    w.write("Hello from parent")
    w.close()
else:
    # Child process
    os.close(w)
    r = os.fdopen(r)
    print(r.read())
    r.close()

2. 消息队列(Message Queue)

消息队列是操作系统提供的一种消息传递机制,允许一个或多个进程将消息放入队列中,其他进程可以从队列中读取消息。消息队列提供了有序的消息传递方式。

import sysv_ipc

key = 1234
mq = sysv_ipc.MessageQueue(key, sysv_ipc.IPC_CREAT)

pid = os.fork()
if pid > 0:
    mq.send(b"Hello from parent")
else:
    message, _ = mq.receive()
    print(message.decode())

3. 共享内存(Shared Memory)

共享内存是一种高效的进程间通信方式,多个进程可以直接读写同一块内存区域。由于内存是共享的,进程之间不需要进行数据的复制,但需要使用同步机制(如信号量)来避免竞争条件和保证数据一致性。

import mmap
import os

size = 1024
shm = mmap.mmap(-1, size)

pid = os.fork()
if pid > 0:
    shm.write(b"Hello from parent")
else:
    shm.seek(0)
    print(shm.read(size).decode())

4. 信号(Signal)

信号是一种用于进程间通知的机制,一个进程可以向另一个进程发送信号,通知其发生了某个事件。信号主要用于进程间的简单通信和控制,如终止进程、暂停进程等。

import os
import signal

def handler(signum, frame):
    print("Received signal:", signum)

signal.signal(signal.SIGUSR1, handler)

pid = os.fork()
if pid > 0:
    os.kill(pid, signal.SIGUSR1)
else:
    signal.pause()

5. 套接字(Socket)

套接字是一种更通用的通信机制,既可以用于同一台机器上的进程间通信,也可以用于网络上的进程间通信。套接字支持多种协议,如TCP和UDP,提供了强大的通信能力。

import socket
import os

def server():
    s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
    s.bind("/tmp/socket")
    s.listen(1)
    conn, _ = s.accept()
    print(conn.recv(1024).decode())
    conn.close()
    s.close()

def client():
    s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
    s.connect("/tmp/socket")
    s.send(b"Hello from client")
    s.close()

pid = os.fork()
if pid > 0:
    server()
else:
    client()

6. 信号量(Semaphore)
信号量是一种用于同步进程或线程的机制,可以用来控制对公共资源的访问。信号量通过计数器来控制访问资源的进程数,是一种有效的并发控制手段。

7. 内存映射文件(Memory-Mapped File)
内存映射文件是一种将文件映射到进程的地址空间的技术,多个进程可以通过内存映射文件共享数据。内存映射文件不仅可以用于进程间通信,还可以用于文件I/O操作。

进程之间同步

进程之间同步是指协调多个进程的执行顺序,以确保共享资源的正确访问和操作。进程同步的目的是避免竞争条件和数据不一致问题。常见的进程同步机制包括:

1. 信号量(Semaphore)

信号量是一种同步机制,可以控制多个进程对共享资源的访问。信号量通过一个计数器来管理资源的使用,保证在资源可用时允许进程访问资源,否则进程会被阻塞。

import multiprocessing
import time

def worker(semaphore, num):
    semaphore.acquire()
    print(f"Worker {num} is working")
    time.sleep(1)
    print(f"Worker {num} is done")
    semaphore.release()

if __name__ == "__main__":
    semaphore = multiprocessing.Semaphore(2)  # 允许最多2个进程同时运行
    processes = [multiprocessing.Process(target=worker, args=(semaphore, i)) for i in range(5)]

    for p in processes:
        p.start()

    for p in processes:
        p.join()

2. 互斥锁(Mutex)

互斥锁用于确保同时只有一个进程可以访问共享资源,避免多个进程同时修改数据而导致数据不一致。

import multiprocessing
import time

def worker(lock, shared_list, num):
    with lock:
        shared_list.append(num)
        print(f"Worker {num} appended {num} to list")
        time.sleep(1)

if __name__ == "__main__":
    lock = multiprocessing.Lock()
    manager = multiprocessing.Manager()
    shared_list = manager.list()

    processes = [multiprocessing.Process(target=worker, args=(lock, shared_list, i)) for i in range(5)]

    for p in processes:
        p.start()

    for p in processes:
        p.join()

    print(f"Shared list: {list(shared_list)}")

3. 条件变量(Condition Variable)
条件变量用于在复杂的同步场景中,允许一个或多个进程等待某个条件并在条件满足时被唤醒。

import multiprocessing
import time

def worker(condition, shared_data, num):
    with condition:
        condition.wait()  # 等待条件满足
        shared_data.append(num)
        print(f"Worker {num} appended {num} to shared data")

def notifier(condition, shared_data):
    time.sleep(2)
    with condition:
        shared_data.append(100)
        print("Notifier appended 100 to shared data")
        condition.notify_all()  # 通知所有等待的进程

if __name__ == "__main__":
    condition = multiprocessing.Condition()
    manager = multiprocessing.Manager()
    shared_data = manager.list()

    processes = [multiprocessing.Process(target=worker, args=(condition, shared_data, i)) for i in range(5)]
    notifier_process = multiprocessing.Process(target=notifier, args=(condition, shared_data))

    for p in processes:
        p.start()

    notifier_process.start()

    for p in processes:
        p.join()

    notifier_process.join()

    print(f"Shared data: {list(shared_data)}")

4. 文件锁(File Lock)

文件锁用于同步对文件的访问。通过锁文件来确保在同一时间只有一个进程可以访问文件。

import fcntl
import time
import os

def worker(num):
    with open("shared_file.txt", "a") as f:
        fcntl.flock(f, fcntl.LOCK_EX)
        f.write(f"Worker {num} is writing to the file\n")
        time.sleep(1)
        fcntl.flock(f, fcntl.LOCK_UN)

if __name__ == "__main__":
    processes = [os.fork() for _ in range(5)]

    for i, pid in enumerate(processes):
        if pid == 0:  # 子进程
            worker(i)
            os._exit(0)

    for _ in processes:
        os.wait()  # 等待所有子进程完成

5. 读写锁(Read-Write Lock)
允许多读单写的访问模式,以提高并发性。

6. 屏障(Barrier)
让多个进程或线程等待,直到所有参与者都到达屏障,然后同时继续执行。

3. 线程管理

线程的生命周期

  • 新建(New):线程对象被创建,但未启动。
  • 就绪(Runnable):线程已经启动并等待CPU时间片。
  • 运行(Running):线程正在执行代码。
  • 阻塞(Blocked):线程因等待资源而暂停执行。
  • 死亡(Terminated):线程执行完毕或因异常退出。

线程之间通信

线程之间的通信主要通过共享内存同步机制来实现。由于线程共享同一进程的地址空间,线程之间的通信比进程间通信更加高效,但需要注意线程同步,以避免竞态条件。以下是几种常用的线程间通信方式及其具体实现:

1. 共享变量

线程可以通过共享变量来通信。需要使用同步机制来保证数据一致性。

import threading

shared_data = 0
lock = threading.Lock()

def thread1():
    global shared_data
    with lock:
        shared_data += 1
        print(f"Thread 1: {shared_data}")

def thread2():
    global shared_data
    with lock:
        shared_data += 1
        print(f"Thread 2: {shared_data}")

t1 = threading.Thread(target=thread1)
t2 = threading.Thread(target=thread2)

t1.start()
t2.start()

t1.join()
t2.join()

2. 队列(Queue)

队列是线程安全的数据结构,可以在线程之间安全地传递数据。

import threading
import queue

q = queue.Queue()

def producer():
    for i in range(5):
        q.put(i)
        print(f"Produced: {i}")

def consumer():
    while True:
        item = q.get()
        if item is None:
            break
        print(f"Consumed: {item}")

t1 = threading.Thread(target=producer)
t2 = threading.Thread(target=consumer)

t1.start()
t2.start()

t1.join()
q.put(None)
t2.join()

3. 事件(Event)

事件是用于线程间通信的同步原语,一个线程可以等待另一个线程的事件信号。

import threading

event = threading.Event()

def waiter():
    print("Waiting for event...")
    event.wait()
    print("Event received!")

def setter():
    print("Setting event...")
    event.set()

t1 = threading.Thread(target=waiter)
t2 = threading.Thread(target=setter)

t1.start()
t2.start()

t1.join()
t2.join()

4. 条件变量(Condition)

条件变量用于在复杂的线程同步场景中,允许一个线程等待特定的条件并通知其他线程条件已满足。

import threading

condition = threading.Condition()
data = None

def producer():
    global data
    with condition:
        data = "Important data"
        condition.notify()

def consumer():
    global data
    with condition:
        condition.wait()
        print(f"Consumed: {data}")

t1 = threading.Thread(target=producer)
t2 = threading.Thread(target=consumer)

t1.start()
t2.start()

t1.join()
t2.join()

5. 信号量(Semaphore)

信号量是一种用于控制对公共资源访问的同步机制。

import threading

semaphore = threading.Semaphore(0)

def producer():
    print("Producing...")
    semaphore.release()

def consumer():
    semaphore.acquire()
    print("Consuming...")

t1 = threading.Thread(target=producer)
t2 = threading.Thread(target=consumer)

t1.start()
t2.start()

t1.join()
t2.join()

线程之间保证安全

在多线程编程中,保证线程安全至关重要,因为多个线程同时访问和修改共享资源可能会导致竞态条件、数据不一致和其他错误。以下是一些常用的多线程安全机制和策略:

1. 互斥锁(Mutex)

互斥锁用于保护共享资源,每次只有一个线程可以获得锁并访问共享资源,从而避免竞态条件。

import threading

lock = threading.Lock()
shared_data = 0

def thread_function():
    global shared_data
    with lock:
        for _ in range(1000):
            shared_data += 1

threads = [threading.Thread(target=thread_function) for _ in range(10)]

for thread in threads:
    thread.start()

for thread in threads:
    thread.join()

print(shared_data)

2. 递归锁(Reentrant Lock)

递归锁允许同一个线程多次获取同一个锁,而不会导致死锁。这对于需要递归调用或多个函数都需要同一个锁的情况很有用。

import threading

rlock = threading.RLock()
shared_data = 0

def thread_function():
    global shared_data
    with rlock:
        with rlock:
            for _ in range(1000):
                shared_data += 1

threads = [threading.Thread(target=thread_function) for _ in range(10)]

for thread in threads:
    thread.start()

for thread in threads:
    thread.join()

print(shared_data)

3. 信号量(Semaphore)

信号量用于控制同时访问某个资源的线程数,可以用来限制对资源的并发访问。

import threading

semaphore = threading.Semaphore(3)

def thread_function(identifier):
    semaphore.acquire()
    print(f"Thread {identifier} is running")
    semaphore.release()

threads = [threading.Thread(target=thread_function, args=(i,)) for i in range(10)]

for thread in threads:
    thread.start()

for thread in threads:
    thread.join()

4. 条件变量(Condition Variable)

条件变量用于线程之间的复杂同步,使一个线程等待另一个线程满足某个条件后再继续执行。

import threading

condition = threading.Condition()
data = None

def consumer():
    global data
    with condition:
        while data is None:
            condition.wait()
        print(f"Consumed: {data}")

def producer():
    global data
    with condition:
        data = "Important data"
        condition.notify_all()

threads = [threading.Thread(target=consumer) for _ in range(3)]
producer_thread = threading.Thread(target=producer)

for thread in threads:
    thread.start()

producer_thread.start()

for thread in threads:
    thread.join()
producer_thread.join()

5. 线程本地存储(Thread Local Storage)

线程本地存储为每个线程提供独立的存储空间,避免多个线程之间的数据冲突。

import threading

thread_local_data = threading.local()

def thread_function(value):
    thread_local_data.value = value
    print(f"Thread {threading.current_thread().name} has value {thread_local_data.value}")

threads = [threading.Thread(target=thread_function, args=(i,)) for i in range(5)]

for thread in threads:
    thread.start()

for thread in threads:
    thread.join()

6. 原子操作

在某些情况下,可以使用原子操作来避免竞态条件。原子操作是不可分割的操作,确保多个线程不会在操作中断点进行切换。

import queue
import threading

q = queue.Queue()

def producer():
    for i in range(10):
        q.put(i)

def consumer():
    while True:
        item = q.get()
        if item is None:
            break
        print(f"Consumed: {item}")

threads = []
producer_thread = threading.Thread(target=producer)
consumer_thread = threading.Thread(target=consumer)

threads.append(producer_thread)
threads.append(consumer_thread)

for thread in threads:
    thread.start()

for thread in threads:
    thread.join()

q.put(None)

死锁

死锁(Deadlock)是指在计算机系统中,两个或多个进程或线程因为竞争资源而互相等待,从而都无法继续执行的一种情况。简单来说,死锁就是一种永久等待的状态,所有参与的进程或线程都无法进行下去。

四个必要条件

互斥条件(Mutual Exclusion):
至少有一个资源必须是非共享的,即该资源在一段时间内只能被一个进程占用。

请求与保持条件(Hold and Wait):
一个进程已经持有至少一个资源,并且还在请求新的资源,而新的资源被其他进程占用。

不剥夺条件(No Preemption):
已经分配给进程的资源不能被强制剥夺,只能由持有该资源的进程自行释放。

循环等待条件(Circular Wait):
存在一个进程集合,其中每个进程都在等待另一个进程持有的资源,形成一个环形等待链。

import threading
import time

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

def thread1():
    with lock1:
        print("Thread 1 acquired lock1")
        time.sleep(1)
        with lock2:
            print("Thread 1 acquired lock2")

def thread2():
    with lock2:
        print("Thread 2 acquired lock2")
        time.sleep(1)
        with lock1:
            print("Thread 2 acquired lock1")

t1 = threading.Thread(target=thread1)
t2 = threading.Thread(target=thread2)

t1.start()
t2.start()

t1.join()
t2.join()

死锁的预防与避免

预防死锁:

  • 破坏互斥条件:确保资源可以被多个进程同时访问。
  • 破坏请求与保持条件:进程在请求资源时,必须先释放所有已持有的资源。
  • 破坏不剥夺条件:如果一个进程请求新的资源而得不到,则必须释放已经持有的资源。
  • 破坏循环等待条件:采用资源有序分配的方式,确保不会形成循环等待。

避免死锁:

  • 使用银行家算法(Banker’s Algorithm)来动态检测并避免资源分配导致的死锁。

检测和恢复:

  • 定期运行死锁检测算法,检测系统中是否存在死锁。
  • 一旦检测到死锁,通过中止其中的一个或多个进程,或强制回收资源来打破死锁。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值