前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Python3 多进程与多线程

Python3 多进程与多线程

作者头像
嵌入式视觉
发布2022-09-05 14:02:02
4190
发布2022-09-05 14:02:02
举报
文章被收录于专栏:嵌入式视觉

Contents

进程与线程

进程和线程是操作系统层面的概念,本质上就是两个操作系统内核对象:即操作系统定义的两个数据结构,操作系统通过这两个数据结构,来管理程序的运行。 (1)以多进程形式,允许多个任务同时运行; (2)以多线程形式,允许单个任务分成不同的部分运行; (3)提供协调机制,一方面防止进程之间和线程之间产生冲突,另一方面允许进程之间和线程之间共享资源。

多进程与多线程

从概念上讲,对于操作系统来说,一个任务就是一个进程(Process),而进程内的”子任务”称为线程(Thread),一个进程至少有一个线程。具有多核cpu的电脑,可以真正实现物理上的多进程。 多任务的实现有3种方式:

  • 多进程模式;
  • 多线程模式;
  • 多进程+多线程模式。

多进程与多线程的程序涉及到同步、数据共享的问题,所以程序编写更复杂些。

为何需要多线程(多进程)

多线程(多进程)能让我们实现并发编程,并发技术,就是可以让我们在同一时间同时执行多条任务的技术。

多进程

Python3 实现多进程(multiprocessing),对于 linux 系统可以直接使用 fork() 调用,windows 系统可以使用内置 multiprocessing 模块。创建进程的代码一定要放在 if __name__ == '__main__' 里面。multiprocessing 模块简单例子如下:

代码语言:javascript
复制
from multiprocessing import Process
import os
# 子进程要执行的代码
def run_proc(name):
    print('RUn child process %s (%s)...' % (name, os.getpid()))
if __name__ == '__main__':
    print('Paraent process %s.' % os.getpid())
    p = Process(target=run_proc, args=('test', ))
    print('Child process will start')
    p.start()
    p.join()
    print('Child process end.')

Paraent process 9400. Child process will start RUn child process test (9451)… Child process end. os.getpid()函数返回当前进程pid.

pool 创建大量子进程

pool 使用程序示例:

代码语言:javascript
复制
from multiprocessing import Pool
import os, time, random
def long_time_task(name):
    print('Run task %s (%s)...' % (name, os.getpid()))
    start = time.time()
    time.sleep(random.random() * 3)
    end = time.time()
    print('Task %s runs %0.2f seconds.' % (name, (end - start)))
if __name__=='__main__':
    print('Parent process %s.' % os.getpid())
    p = Pool(4)
    for i in range(5):
        p.apply_async(long_time_task, args=(i,))
    print('Waiting for all subprocesses done...')
    p.close()
    p.join()
    print('All subprocesses done.')

Parent process 9400. Run task 0 (10364)… Run task 2 (10366)… Run task 1 (10365)… Run task 3 (10367)… Waiting for all subprocesses done… Task 3 runs 0.73 seconds. Run task 4 (10367)… Task 1 runs 1.16 seconds. Task 2 runs 1.33 seconds. Task 4 runs 0.59 seconds. Task 0 runs 2.60 seconds. All subprocesses done.

对 pool 对象调用 join() 方法会等待所有子进程执行完毕,然后执行后续语句,调用 join() 方法之前必须调用 close(),调用 close() 之后就不能继续添加新的 Process 了。

子进程

进程间通信

Python 的 multiprocessing 模块包装了底层机制,提供了 Queue、Pipes 等多种方式来交换数据。

多线程

多任务可以由多进程完成,也可以由一个进程内的多线程完成。启动一个线程就是把一个函数传入并创建 Thread 实例,然后调用 start() 开始执行,多线程的简单示例代码如下:

代码语言:javascript
复制
import time, threading
# 新线程执行的代码
def loop():
    print('thread %s is running...' % threading.current_thread().name)
    n = 0
    while n < 5:
          n = n+1
          print('thread %s >>> %s' % (threading.current_thread().name, n))
          time.sleep(2)
    print('thread %s ended.' % threading.current_thread().name)
print('thread %s is running..' % threading.current_thread().name)
t = threading.Thread(target=loop, name='LoopThread')
t.start()
t.join()
print('thread %s ended.' % threading.current_thread().name)

thread MainThread is running.. thread LoopThread is running… thread LoopThread >>> 1 thread LoopThread >>> 2 thread LoopThread >>> 3 thread LoopThread >>> 4 thread LoopThread >>> 5 thread LoopThread ended. thread MainThread ended.

由于任何进程默认就会启动一个线程,我们把该线程称为主线程,主线程又可以启动新的线程, Python 的 threading 模块有个 current_thread()函数,它永远返回当前线程的实例。主线程实例的名字叫 MainThread,子线程的名字在创建时指定,我们用 LoopThread 命名子线程。名字仅仅在打印时用来显示,完全没有其他意义,如果不起名字 Python 就自动给线程命名为 Thread-1, Thread-2……

另一种多线程示例代码如下:

代码语言:javascript
复制
import threading
def doubler(number):
    """
    可以被线程使用的一个函数
    """
    print(threading.currentThread().getName() + '\n')
    print(number * 2)
    # print()
if __name__ == '__main__':
    for i in range(5):
        my_thread = threading.Thread(target=doubler, args=(i,))
        my_thread.start()

代码运行后,输出如下

Thread-1 0 Thread-2 2 Thread-3 4 Thread-4 6 Thread-5 8

Lock

多线程与多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,而多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据的最大危险在于多个线程同时改一个变量,把内容改乱了。

创建线程锁通过 threading.Lock() 来实现,获得锁的线程用完后一定要释放锁,否则那些苦苦等待锁的线程将永远等待下去,成为死线程。所以我们用 try...finally 机制来确保锁一定会被释放。

多进程 vs 多线程

Python 中的多线程最好用于处理有关 I/O 的操作,如从网上下载资源或者从本地读取文件或者目录。如果你要做的是 CPU 密集型操作,那么你需要使用 Python 的 multiprocessing 多进程模块。这样做的原因是,Python 有一个全局解释器锁 (GIL),使得所有子线程都必须运行在同一个主线程中, GIL 导致了 导致 Python 中的多线程并不是并行执行,而是“交替执行” 。正因为如此,当你通过多线程来处理多个 CPU 密集型任务时,你会发现它实际上运行的更慢。因此,我们将重点放在那些多线程最擅长的领域:I/O 操作

而所谓 Python 多线程适合 I/O 密集型任务,指的是,当一个线程处于 IO 阻塞状态时会释放 GIL 锁,那么这个时候其他线程就可以获得锁然后进行发送数据,当这个线程发送完处于 IO 阻塞后,又可以被第三个线程拿到 GIL 锁进行 IO 发送数据的操作,所以一个时间片内会出现一个线程在发送数据,另个线程在传输数据,这样就减少了 IO 传输时间

全局锁问题

Python 的全局锁问题 (GIL),我在网上看过很多文章,但是大多数泛泛而谈,没有经过认真思考和实践总结而写的片面性文章,直到 2020.11.16 日这天阅读《Python + Cookbook》第三版这本书籍,才开始慢慢理解 GIL 和多线程的关系。

值得一提的是,这本书翻译的质量确实不好,但是类似的书籍我目前也没有找到,想要深入学习 Python 编程(不适合初学者)的可以学习下这本书。网上链接在这里

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-11-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 进程与线程
    • 多进程与多线程
      • 为何需要多线程(多进程)
      • 多进程
        • pool 创建大量子进程
          • 子进程
            • 进程间通信
            • 多线程
              • Lock
              • 多进程 vs 多线程
              • 全局锁问题
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档