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)来动态检测并避免资源分配导致的死锁。
检测和恢复:
- 定期运行死锁检测算法,检测系统中是否存在死锁。
- 一旦检测到死锁,通过中止其中的一个或多个进程,或强制回收资源来打破死锁。