视频加载失败

多进程、多线程与协程创建模板

3476 字
17 分钟
多进程、多线程与协程创建模板

【一】开启多进程的创建模板(基于Process模块)#

【1】方式一:创建多进程#

import multiprocessing
def worker(n):
"""子进程要执行的任务"""
print(f'子进程 {n} 正在执行')
def main():
task_lists = []
for i in range(100)
# 创建一个进程对象
p = multiprocessing.Process(target=worker, args=(i,))
# 启动进程
p.start()
# 将子进程添加到进程列表里
task_lists.append(p)
for p in task_lists:
# 等待子进程结束
p.join()
print('主进程结束')
if __name__ == '__main__':
main()
  • 在上面的例子中,我们定义了一个名为 worker() 的函数,它将作为子进程执行的任务。

    • 使用 multiprocessing.Process() 创建进程对象时需传递一个参数,即要执行的方法名或函数名。
    • 在本例中,我们将 worker() 函数作为子进程的任务函数。
  • 调用 p.start() 方法启动该进程。此时,将自动执行实现了 worker() 函数的子进程,并输出 "子进程正在执行" 信息。

  • 最后调用 p.join() 方法等待子进程结束。因为主进程会继续往下执行,而如果子进程尚未完成就结束了程序,那么子进程将被终止而无法正常执行。

  • 以上是基于 Process 方法创建进程对象的示例。

【2】方式二:类的继承#

import multiprocessing
class MyProcess(multiprocessing.Process):
def __init__(self, arg1, arg2):
super(MyProcess, self).__init__() # 调用父类的初始化方法
self.arg1 = arg1
self.arg2 = arg2
def run(self):
"""重载父类中的run方法,实现进程执行的逻辑"""
print('子进程启动')
# 实现进程的逻辑代码
if __name__ == '__main__':
p = MyProcess(arg1_value, arg2_value) # 创建进程对象
p.start() # 启动子进程
p.join() # 等待子进程结束
print('主进程结束')
  • 在这个模板中,我们定义了一个 MyProcess 类,它继承了 multiprocessing.Process 类。
    • 当创建该类的对象时,需要传入两个参数 arg1 和 arg2,在 MyProcess 类的构造函数中,我们将这两个参数保存起来,并调用父类的 __init__() 方法进行初始化。
  • 接着,在 MyProcess 类中
    • 我们重载了父类中的 run() 方法,在里面实现了子进程的逻辑。
    • 当 p.start() 方法调用时,将自动启动子进程,并执行 run() 方法中的代码块。
  • 最后,我们调用 p.join() 方法等待子进程完成,然后输出 "主进程结束" 的信息。

【二】生产者与消费者模型(JoinableQueue模块)#

  • 生产者-消费者模型是指在并发编程中
    • 有一个或多个生产者向共享的缓冲区内放入数据,同时也有一个或多个消费者从共享缓冲区内取出数据来进行处理。
  • JoinableQueue是Python标准库中的一种线程安全的队列类型,可以很方便地实现生产者-消费者模型。
  • JoinableQueue类提供了put和get方法来操作队列。
    • put方法用于将数据放入队列中
    • get方法则用于从队列中取出数据。
    • 这两个方法都支持阻塞和非阻塞调用。
  • 在JoinableQueue中,除了put和get方法
    • 还提供了task_done和join方法。
    • 其中,task_done方法用来通知队列中的一个任务已经完成
    • join方法则用来等待所有任务完成。
  • 使用JoinableQueue模块,可以很方便地实现生产者-消费者模型,代码如下:
from queue import JoinableQueue
import threading
# 定义生产者线程
class Producer(threading.Thread):
def __init__(self, queue):
threading.Thread.__init__(self)
self.queue = queue
def run(self):
for i in range(10):
item = "item" + str(i)
print("Producing ", item)
self.queue.put(item)
# 生产完成后发送结束信号
self.queue.join()
print("Production is over.")
# 定义消费者线程
class Consumer(threading.Thread):
def __init__(self, queue):
threading.Thread.__init__(self)
self.queue = queue
def run(self):
while True:
item = self.queue.get()
print("Consuming", item)
self.queue.task_done()
queue = JoinableQueue()
# 创建多个生产者和消费者线程
for i in range(3):
t = Producer(queue)
t.start()
for i in range(2):
t = Consumer(queue)
t.start()
  • 在这个例子中,定义了两个线程类Producer和Consumer,分别实现生产者和消费者的功能。
    • 创建三个Producer线程,两个Consumer线程,通过JoinableQueue来进行生产者和消费者之间线程安全的数据交换。
  • 当所有生产者都向队列中放入数据时,每一个Producer线程会调用队列的join方法,等待所有任务完成。
    • 而每个Consumer线程则不断地从队列中取出数据,并通过调用队列的task_done方法来通知加入的消费者已经处理完一个任务。
  • 当所有加入的task_done被调用后,join方法就会返回。

【三】开启多线程的创建模板(基于Thread模块)#

【1】方式一 :创建多线程#

import threading
# 定义线程执行的任务函数
def task(n):
# 线程要执行的代码
print(n)
pass
def main(num_threads):
threads = []
for i in range(num_threads):
# 创建线程对象
t = threading.Thread(target=task, args=(i,))
threads.append(t)
# 启动线程
t.start()
# 等待所有线程执行完毕
for t in threads:
# 主线程等待子线程执行完毕
t.join()
if __name__ == '__main__':
main(100)
  • 在上面的代码中,我们首先定义了一个线程要执行的任务函数task,并使用threading.Thread类创建了一个名为t的线程对象。
    • 该对象被赋值一个目标函数target(即要执行的任务函数),而其他参数都有默认值。
    • 然后我们使用t.start()方法启动线程
    • 并使用t.join()方法使主线程等待子线程执行完毕。
    • 这样做可以确保在主线程结束之前所有子线程都已完成执行。
  • 在这段代码中,我们首先定义了一个空的列表用于存储所有的线程对象。
    • 然后使用循环和threading.Thread类创建指定数量的线程对象并分别启动它们。
    • 最后我们遍历线程列表并调用每个线程对象的join方法以等待它们全部执行完毕。

【二】方式二:类的继承#

下面是一个基于Thread模块的多线程创建模板,使用了类继承的方式:

import threading
# 自定义一个继承自Thread类的子类
class MyThread(threading.Thread):
def __init__(self, threadID, name, counter):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
self.counter = counter
# 重写run方法,即线程启动后执行的方法
def run(self):
print("Starting " + self.name)
# 使用锁机制保证数据同步
threadLock.acquire()
print_time(self.name, self.counter, 3)
threadLock.release()
def print_time(threadName, delay, counter):
while counter:
time.sleep(delay)
print(f"{threadName}: {time.ctime(time.time())}")
counter -= 1
def main():
# 创建锁实例
threadLock = threading.Lock()
# 创建两个线程
thread1 = MyThread(1, "Thread-1", 1)
thread2 = MyThread(2, "Thread-2", 2)
# 启动线程
thread1.start()
thread2.start()
# 等待线程完成
thread1.join()
thread2.join()
print("Exiting Main Thread")
if __name__ == '__main__':
main()
  • 在这个示例中,我们自定义了一个名为MyThread的类,它继承自Thread类,可以通过实例化多个该类的对象来开启多线程。
    • 在MyThread类中,我们需要重写run()方法,这个方法定义了启动线程后要执行的任务。
  • 在本例中,MyThread类有三个参数threadID、name和counter,分别表示线程ID、名称和打印次数。
    • 在__init__()方法中,我们调用了父类Thread的构造函数,并将这三个参数传递给了父类的构造函数。
  • 在主程序中,我们创建了两个线程,即两个MyThread类的实例对象thread1和thread2。
    • 接着,我们使用start()方法启动这两个线程,它们会自动运行各自的run()方法。
  • 由于多个线程可能同时访问某些资源,所以我们必须使用锁机制。
    • 在本例中,我们创建了一个Lock的实例threadLock,并在MyThread类中使用了threadLock.acquire()和threadLock.release()语句来保证对共享资源的安全访问。

【四】守护进程/守护线程#

【1】守护进程#

import time
import threading
# 守护进程函数
def daemon():
print("Daemon started")
while True:
time.sleep(1)
print("Daemon is still running")
# 主程序
if __name__ == '__main__':
t = threading.Thread(target=daemon)
t.daemon = True # 将线程设置为守护进程
t.start()
time.sleep(5) # 主线程等待5秒后结束
print("Main thread exit.")
  • 在这个例子中,我们创建了一个名为daemon的函数,用作一个无限循环的守护进程。
    • 然后我们启动了一个新的线程,并将它标记为守护进程。
  • 在主线程中,我们暂停5秒钟,然后退出程序。
    • 由于我们启动的是一个守护进程,所以当主线程结束时,守护进程也会随之结束。
  • 在输出中,我们可以看到主线程结束后,守护进程也立即停止。
    • 同时,我们发现守护进程的输出一直在运行,即使主线程已经退出。
  • 需要注意的是,在Python中使用守护进程时,守护进程线程中的代码不会像普通线程一样在程序结束时正常地执行完毕,而是会被强制终止。
  • 因此,在编写守护进程的代码时,一定要确保它是安全的,并且不会在被强制终止时留下不良后果。

【2】守护线程#

  • 守护线程是指虽然是线程,但是并不会阻止程序退出的一类特殊线程。
    • 当主线程退出时,所有非守护线程都会被等待执行完毕后再退出,而守护线程则会随着主线程的退出而立即结束。
  • 通常情况下,守护线程被用来执行一些不需要持续进行的任务,例如日志记录、监控等,以避免这些任务因为被遗漏而导致程序异常。
    • 同时,由于守护线程会在主线程结束时自动结束,因此也常用于处理一些无法手动结束的子线程。
  • 以下是一个简单的使用Python多线程模块threading创建守护线程的示例代码:
import threading
import time
def daemon_func():
while True:
print("Daemon thread is running...")
time.sleep(1)
if __name__ == "__main__":
daemon_thread = threading.Thread(target=daemon_func)
daemon_thread.setDaemon(True) # 设置为守护线程
daemon_thread.start()
# 主线程休眠5秒
time.sleep(5)
print("Main thread is exiting...")
  • 在这个示例中,我们首先创建了一个daemon_func()函数,其中包含一个无限循环打印信息的任务。
  • 接着,我们创建了一个新线程daemon_thread来执行daemon_func()函数,并通过设置setDaemon(True)将其设置为守护线程。
  • 在主程序中,我们让主线程休眠5秒,并在其结束前打印一条信息。
  • 由于daemon_thread是守护线程,因此即使该线程正在执行,它也会随着主线程的退出而立即结束。
  • 需要注意的是,当主线程退出时,程序中可能还有其他的非守护线程正在执行,在这种情况下,这些非守护线程将会被继续执行。
  • 因此,需要确保所有需要执行完毕的任务都已经完成后再退出程序。

【五】互斥锁#

互斥锁是用于保障在多线程或多进程环境中共享资源的正确性和完整性的一种同步机制。

它通常用于临界区的保护,即一次只有一个线程或进程可以访问某些共享资源,以避免数据的读写冲突。

  • 在Python中,我们可以使用内置的threading模块来实现互斥锁。
import threading
# 共享资源
count = 0
# 定义互斥锁
lock = threading.Lock()
# 线程函数
def increment():
global count
for i in range(100000):
# 加锁
lock.acquire()
count += 1
# 释放锁
lock.release()
# 创建两个线程并启动
t1 = threading.Thread(target=increment)
t2 = threading.Thread(target=increment)
t1.start()
t2.start()
# 主线程等待子线程执行完毕后输出结果
t1.join()
t2.join()
print(count)
  • 在上述代码中,首先定义了一个所需要同步访问的共享资源count以及一个互斥锁lock。
    • 然后创建两个线程并启动,这两个线程都会调用同一个函数increment来对count进行增加操作。
    • 每个线程在访问共享资源之前都会获得锁,并在访问结束后释放锁,以保证对共享资源的访问是安全的。
  • 值得注意的是,在使用互斥锁时,我们需要特别小心避免死锁的情况
    • 即由于多个线程互相等待各自占有的锁而导致程序陷入无限阻塞。
  • 为了避免死锁,应该在代码中尽量减少加锁的次数和锁的持有时间,以及避免在持有锁的状态下访问可能会申请其他锁的资源。

【六】如何实现TCP服务端并发效果#

实现TCP服务端并发效果的一种常见方法是使用多线程或多进程。

  • 使用多线程的方式,可以为每个客户端连接都创建一个新的线程,使得服务端可以同时处理多个客户端请求。下面是一个简单的使用多线程实现TCP服务端并发的示例代码:
import socket
import threading
def handle_client(conn, addr):
print(f"New connection from {addr}")
while True:
data = conn.recv(1024)
if not data:
break
response = "Echo: " + data.decode()
conn.sendall(response.encode())
print(f"Connection closed from {addr}")
conn.close()
if __name__ == "__main__":
host = "localhost"
port = 8080
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind((host, port))
server_socket.listen()
print(f"Server listening on {host}:{port}")
while True:
conn, addr = server_socket.accept() # 阻塞等待客户端连接
# 创建一个新的线程来处理客户端请求
client_thread = threading.Thread(target=handle_client, args=(conn, addr))
client_thread.start()
  • 在这个示例中,当服务器接收到一个客户端的连接请求后,它会创建一个新的线程来处理这条连接。
    • 在handle_client()函数中,我们通过recv()方法接收客户端发送的数据,并使用sendall()方法将响应数据发送回客户端。
    • 当客户端发送一个空数据时,我们退出循环并关闭连接。
    • 在主程序中,我们使用accept()方法来阻塞等待客户端连接,并在新线程中调用handle_client()函数处理该连接。
  • 值得注意的是,在使用多线程的方式时,需要注意线程安全问题,例如尽量避免共享资源的竞争和使用锁机制等。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!

打赏
多进程、多线程与协程创建模板
https://firefly.cuteleaf.cn/posts/framework/concurrent/多进程和多线程以及协程的创建模板/
作者
Dream
发布于
2023-06-08
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
Dream
Hello, I'm Dream.
公告
欢迎来到我的博客!爱学习爱进步。
分类
标签
最新动态
站点统计
文章
300
分类
5
标签
26
总字数
640,882
运行时长
0 天
最后活动
0 天前
站点信息
构建平台
Vercel
博客版本
Firefly v6.16.8
文章许可
CC BY-NC-SA 4.0
文章目录