概念
进程(process)和线程(thread)是操作系统的基本概念,但是它们比较抽象,不容易掌握。
最近,我读到一篇材料,发现有一个很好的类比,可以把它们解释地清晰易懂。
1.

计算机的核心是CPU,它承担了所有的计算任务。它就像一座工厂,时刻在运行。
2.

假定工厂的电力有限,一次只能供给一个车间使用。也就是说,一个车间开工的时候,其他车间都必须停工。背后的含义就是,单个CPU一次只能运行一个任务。
3.

进程就好比工厂的车间,它代表CPU所能处理的单个任务。任一时刻,CPU总是运行一个进程,其他进程处于非运行状态。
4.

一个车间里,可以有很多工人。他们协同完成一个任务。
5.

线程就好比车间里的工人。一个进程可以包括多个线程。
6.

车间的空间是工人们共享的,比如许多房间是每个工人都可以进出的。这象征一个进程的内存空间是共享的,每个线程都可以使用这些共享内存。
7.

可是,每间房间的大小不同,有些房间最多只能容纳一个人,比如厕所。里面有人的时候,其他人就不能进去了。这代表一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。
8.

一个防止他人进入的简单方法,就是门口加一把锁。先到的人锁上门,后到的人看到上锁,就在门口排队,等锁打开再进去。这就叫"互斥锁"(Mutual exclusion,缩写 Mutex),防止多个线程同时读写某一块内存区域。
9.

还有些房间,可以同时容纳n个人,比如厨房。也就是说,如果人数大于n,多出来的人只能在外面等着。这好比某些内存区域,只能供给固定数目的线程使用。
10.

这时的解决方法,就是在门口挂n把钥匙。进去的人就取一把钥匙,出来时再把钥匙挂回原处。后到的人发现钥匙架空了,就知道必须在门口排队等着了。这种做法叫做"信号量"(Semaphore),用来保证多个线程不会互相冲突。
不难看出,mutex是semaphore的一种特殊情况(n=1时)。也就是说,完全可以用后者替代前者。但是,因为mutex较为简单,且效率高,所以在必须保证资源独占的情况下,还是采用这种设计。
11.

操作系统的设计,因此可以归结为三点:
(1)以多进程形式,允许多个任务同时运行;
(2)以多线程形式,允许单个任务分成不同的部分运行;
(3)提供协调机制,一方面防止进程之间和线程之间产生冲突,另一方面允许进程之间和线程之间共享资源。
多进程切换代价为什么比多线程大
Python的多进程切换代价比多线程大的原因主要源于进程和线程在操作系统中的不同特性以及它们共享资源的方式。
首先,进程是操作系统资源分配的基本单位,每个进程拥有独立的内存空间和系统资源。当进行进程切换时,操作系统需要保存当前进程的上下文环境(包括CPU寄存器状态、程序计数器、内存管理信息、打开的文件状态等),并恢复下一个进程的上下文环境。这个上下文切换的过程涉及大量的数据保存和恢复操作,因此开销相对较大。
相比之下,线程是进程内的执行单元,多个线程共享同一个进程的内存空间和系统资源。线程切换时,由于它们共享相同的内存空间,因此不需要像进程切换那样保存和恢复整个进程的上下文环境。线程切换主要涉及线程控制块(TCB)的切换以及寄存器等少量信息的保存和恢复,因此开销相对较小。
此外,Python的多进程还涉及到进程间通信(IPC)的问题。由于进程间内存是隔离的,因此不同进程之间的数据交换需要通过特定的IPC机制来实现,如管道、共享内存、消息队列等。这些IPC机制本身也会带来一定的开销。而线程间由于共享进程内存,可以直接访问共享数据,无需通过IPC机制,因此在线程间通信方面也具有更高的效率。
综上所述,由于进程和线程在操作系统中的不同特性以及它们共享资源的方式,导致Python的多进程切换代价比多线程大。因此,在选择使用多进程还是多线程时,需要根据具体的应用场景和需求进行权衡。对于计算密集型任务,由于多进程可以利用多核CPU并行处理,因此可能具有更高的性能;而对于IO密集型任务,由于多线程在IO等待时可以切换执行其他线程,因此可能具有更好的响应性。
python如何实现不同进程间的通信
在Python中,有多种方式可以实现不同进程间的通信。下面我将列举几种常用的方法,并结合示例代码来说明。
1. 管道(Pipe)
管道是进程间通信的一种基本方式,它提供了单向的、字节流的通信方式。在Python的multiprocessing模块中,我们可以使用Pipe()函数来创建一个管道。
from multiprocessing import Process, Pipe
def worker(conn):
print(conn.recv()) # 接收数据
conn.send("Data received and processed") # 发送数据
conn.close()
if __name__ == '__main__':
parent_conn, child_conn = Pipe() # 创建一个管道
p = Process(target=worker, args=(child_conn,)) # 创建子进程
p.start()
parent_conn.send("Hello from parent") # 发送数据给子进程
print(parent_conn.recv()) # 接收子进程发来的数据
p.join()
2. 队列(Queue)
队列是另一种常用的进程间通信方式,它支持多个生产者和消费者,提供了线程和进程安全的消息传递。
from multiprocessing import Process, Queue
def worker(q):
print(q.get()) # 从队列中获取数据
q.put("Data processed") # 将数据放入队列
if __name__ == '__main__':
q = Queue() # 创建一个队列
q.put("Hello from main process") # 将数据放入队列
p = Process(target=worker, args=(q,)) # 创建子进程
p.start()
print(q.get()) # 从队列中获取数据
p.join()
3. 共享内存(Value, Array)
除了基于消息的通信方式,我们还可以通过共享内存来实现进程间通信。Python的multiprocessing模块提供了Value和Array数据结构,它们可以在多个进程间共享。
from multiprocessing import Process, Value
import ctypes
def worker(shared_value):
shared_value.value = 10 # 修改共享值
if __name__ == '__main__':
shared_value = Value(ctypes.c_int, 0) # 创建一个共享的整数值
p = Process(target=worker, args=(shared_value,)) # 创建子进程
p.start()
p.join()
print(shared_value.value) # 打印修改后的共享值
4. 套接字(Socket)
套接字是不同进程(甚至不同机器)间通信的一种通用方法。它不仅可以用于本地进程间通信,还可以用于网络通信。
import socket
from multiprocessing import Process
def server_program():
host = socket.gethostname()
port = 5000
server_socket = socket.socket()
server_socket.bind((host, port))
server_socket.listen(1)
conn, address = server_socket.accept()
print("Connection from: " + str(address))
while True:
data = conn.recv(1024).decode()
if not data:
break
print("from connected user: " + str(data))
data = input(' -> ')
conn.send(data.encode())
conn.close()
def client_program():
host = socket.gethostname()
port = 5000
client_socket = socket.socket()
client_socket.connect((host, port))
message = input(" -> ")
while message.lower().strip() != 'bye':
client_socket.send(message.encode())
data = client_socket.recv(1024).decode()
print('received from server: ' + data)
message = input(" -> ")
client_socket.close()
if __name__ == '__main__':
p1 = Process(target=server_program)
p1.start()
p2 = Process(target=client_program)
p2.start()
p1.join()
p2.join()
每种通信方式都有其优缺点,需要根据实际应用场景来选择最适合的通信方式。例如,管道和队列适用于父子进程间的通信,而套接字则更适合于跨机器或跨网络的进程间通信。
python如何实现不同线程间的通信
在Python中,有多种方法可以实现不同线程间的通信。这些方法包括使用queue模块、threading模块的Event和Condition对象,以及共享内存。下面,我将通过示例代码展示如何使用这些方法。
1. 使用queue模块
queue模块提供了一个线程安全的队列类,可以用于线程间的通信。
import threading
import queue
import time
# 定义一个生产者线程
def producer(q):
for item in range(5):
print(f'生产者生产了 {item}')
q.put(item)
time.sleep(1) # 模拟耗时操作
q.put(None) # 使用None作为结束信号
# 定义一个消费者线程
def consumer(q):
while True:
item = q.get()
if item is None:
break # 接收到结束信号,退出循环
print(f'消费者消费了 {item}')
time.sleep(2) # 模拟耗时操作
# 创建一个队列
q = queue.Queue()
# 创建并启动生产者线程
t1 = threading.Thread(target=producer, args=(q,))
t1.start()
# 创建并启动消费者线程
t2 = threading.Thread(target=consumer, args=(q,))
t2.start()
# 等待线程结束
t1.join()
t2.join()
2. 使用threading.Event
Event对象用于线程间的简单通信。一个线程可以等待一个事件被设置,而另一个线程可以在适当的时候设置这个事件。
import threading
import time
# 定义一个工作线程
def worker(event):
print('工作线程等待事件被设置...')
event.wait() # 等待事件被设置
print('事件被设置了,工作线程开始工作...')
# 创建一个Event对象
event = threading.Event()
# 创建并启动工作线程
t = threading.Thread(target=worker, args=(event,))
t.start()
# 主线程等待一段时间
time.sleep(2)
print('主线程设置事件...')
event.set() # 设置事件
# 等待工作线程结束
t.join()
3. 使用threading.Condition
Condition对象允许线程等待某个条件成立,或者通知其他线程某个条件已经成立。
import threading
# 定义一个带有条件的线程
def condition_thread(cond):
with cond:
print('线程等待条件...')
cond.wait() # 等待条件成立
print('条件成立了,线程继续执行...')
# 创建一个Condition对象
cond = threading.Condition()
# 创建并启动线程
t = threading.Thread(target=condition_thread, args=(cond,))
t.start()
# 主线程等待一段时间
time.sleep(2)
# 通知线程条件已经成立
with cond:
print('主线程通知条件成立...')
cond.notify() # 通知等待的线程
# 等待线程结束
t.join()
4. 使用共享内存
在某些情况下,可以使用共享内存来实现线程间的通信。这通常涉及到使用threading.Lock或其他同步机制来确保数据的一致性。
import threading
# 定义一个共享数据
shared_data = {'value': 0}
lock = threading.Lock()
# 定义一个增加共享数据值的线程
def increment_thread():
global shared_data
for _ in range(100000):
with lock:
shared_data['value'] += 1
# 创建并启动多个线程
threads = []
for _ in range(10):
t = threading.Thread(target=increment_thread)
t.start()
threads.append(t)
# 等待所有线程结束
for t in threads:
t.join()
print(f'共享数据的最终值: {shared_data["value"]}')
请注意,在使用共享内存时,要特别小心数据竞争和死锁等问题。确保使用适当的锁或其他同步机制来保护共享数据。
每种方法都有其适用的场景,你可以根据具体的需求选择最适合你的通信方式。在实践中,通常建议使用queue模块来实现线程间的通信,因为它提供了线程安全的队列操作,使得通信更加简单和可靠。