python 多线程&多进程

refer

  • daemon thread 和linux里面说的(daemon)守护进程没什么关系

    甚至和linux的守护进程概念相反,前者是确保父进程退出(用户通过主进程关闭)时,子进程也会被杀死;后者是确保linux开机后,一些程序不会随着用户临时会话的结束而结束。

  • key feature
  1. background task --> Once join() is used , whether deamon attribute is True is not important
  2. only useful when the main program is running
  3. ok to kill
  • daemon process
  • apply是为了兼容之前的写法
  • apply( xxx )相当于apply_sync( xxx ).get(),apply_sync是非阻塞的,但是get函数是阻塞的,那么就相当于apply是阻塞的
  • map就是新的API, 不过效果是一样的。apply实现比较灵活,毕竟是一个process apply一次,所以每个process可以执行不同的程序,但是map的话就是一个pool的process集中一次apply,每个process就只能执行一个相同的函数。
  • starmap和map的区别 :后者本来是只能给函数传入一个参数的,前者就是为了弥补这项功能上的缺失才出现的API。但其实后者可以通过把多个参数打包的方式进行参数传递。所以感觉前者也没有多大的不可替代性。

multi thread

  • GIL 锁住的是解释器
  • IO密集型

multi core

  • 计算密集型

comparasion( same & not same )

same

.start()
.run()
.join()
  • 不加join都默认杀不掉,而且主进程很大可能会乱序执行(跟子进程/线程混在一起)
    ( join的话,后面的程序会等到子进程都执行完再执行。)

not same

  • porcess
pool= mp.Pool( POOLSIZE:int=0 ) # 0 means use all cpu
results =[]
# method 1
results = pool.map( target , args:iterable of tuples )
for res in results :
	print(res)

# method 2
for i in range( x ) :
	results .append(pool.apply_async(target , args))	 # 非阻塞
	results .append(pool.apply(target , args:tuple))  # 阻塞

pool.close() # 关闭进程池,表示不能在往进程池中添加进程
pool.join() # 等待进程池中的所有进程执行完毕,必须在close()/terminate()之后调用

for res in results :
	print(res.get())  # 注意这里的get哦,因为这里返回的是一个结果对象
  • apply_async和apply的区别,懒人可以直接看人家的实验结果
  • 也有第三种方法就是pool.map(func, iterable[, chunksize]),它会使进程阻塞与此直到结果返回
    map_async是非阻塞的
  • process类的构造函数

init(self, group=None, target=None, name=None, args=(), kwargs={})


thread-safe data structure

  • Queue , Value ,Array

concurrent.futures并发库

Python3废弃了原来的thread模块,换成了高级的threading模块,concurrent.futures是使用线程的最新方式。(Python3把thread模块重命名为_thread,以此强调这是低层实现, 不应该在应用代码中使用)如果使用场景较复杂,需要更高级的工具multiprocessing模块和threading模块。

concurrent.futures 是python3新增加的一个库,用于并发处理,提供了多线程和多进程的并发功能
类似于其他语言里的线程池(也有一个进程池),他属于上层的封装,对于用户来说,不用在考虑那么多东西了。

import os
from concurrent import futures
import time
# print(os.cpu_count())

def test(num):
    time.sleep(10-num)
    return num
 
data=[1,5,9]
with futures.ProcessPoolExecutor(max_workers=os.cpu_count()) as executor:
    for future in executor.map(test,data):
        print(future)

  • 默认会等待所有进程都运行完再返回结果
  • executor.map(test,data[,chunksize=?])返回的是一个Future对象的列表

看了源码 以后易知,map是调用父类(抽象类)的map函数,which调用子类的submit函数,which顺序保持不变,但是几个任务是并行执行的。所以返回的列表的res对应顺序跟传入的是一致的。

  • 几个编程注意点
    • 指定了chunksize之后,传入的并不会使一个list/array,实际上还是单个元素,可以看源码里面发现,用的是itertools.islice() 和 partial函数的结合,which means是一个一个元素传入的。简单来说,指定chunksize前后,代码编写应该是一致的【经过编程验证】
    • 看了源码 以后易知,map是调用父类(抽象类)的map函数,which调用子类的submit函数,which顺序保持不变,但是几个任务是并行执行的。所以返回的列表的res对应顺序跟传入的是一致的。【经过编程验证】

misc

  • 几种多进程的启动方式

    • reference
    • spawn在 Windows and macOS上是默认的多进程启动方法, fork是unix默认的方式。
    • fork使用os.fork()去fork python的解释器,子进程创建的时候,实际上和父进程是等价的,所有的资源都会被子进程继承。

      Note that safely forking a multithreaded process is problematic.

    • spawn的子进程进继承必要的运行run方法的资源,尤其是unnecessary file descriptors and handles不会被继承。启动会相对另外两种方式慢很多。
    • forkserver主要跟文件描述符有关 ( tl;dr
  • 进程、线程、协程的一些背景知识

    • 进程是资源分配的单位,线程是操作系统调度的基本单位(一个进程默认会有一个线程,这个default的线程就是主线程。如果没有做多线程的话,就只有一个主线程来做这个进程要做的事情)。

    • 这个b站视频在说进程和线程之间的区别上不错

      • 进程是一段资源(多少核CPU等等)和内存的的代表,不具体执行某个任务,具体执行还是线程来做。
      • 其实线程和进程逻辑上并不需要一个隶属的关系,只是一个进程要至少对应一个线程罢了。
        • 很多人会说一个进程至少有一个主线程,这种说法,对,也不对。更精准的用词,应该是用“对应”。在用户态,进程觉得自己拥有一个主线程,但其实这个用户空间的线程会对应的内核空间里的线程。 所以一个进程的主线程对应的内核中真正的线程,也许跟另一个进程是一样的(只不过内核线程在执行不同任务的时候,会读取进程(或者用户空间的线程)保存下来的上下文然后接着执行)。所以进程和内核线程之间的关系,可以是1:1,可以是1:n,也可以是m:n
          在这里插入图片描述
          在这里插入图片描述
      • 也说到了协程为什么出现的一个原因,实际上线程的切换也是在内核里面进行的,这意味着其实线程的切换开销其实也不小(还有优化的空间)。

        协程就是希望不走kernel进行一些切换,多个协程对应于一个线程
        在这里插入图片描述
        e.g. 有一些IO产生的时候,如果我们不管,操作系统给DMA发出指令之后(见下面那个视频讲解)会调度另一个内核线程进来工作以占用CPU(那这就是内核里面的切换了,引起了切换开销)。但是协程的话,可以及时找另一个非IO让同一个内核线程继续进行CPU计算,防止被他切换掉 [这句关于协程的工作机制是我自己脑补的。我也没写过哈哈,也没有详细去找资料验证一下]
    • 这个b站视频说如下两点不错

      • 为什么要有线程?1-1-1-2-2-2-3-3-3 跟 1-2-3-1-2-3-1-2-3 这样来回切换效果一样,但是后者不需要切换不是省下很多开销么?
        • DMA的复用 和 中断的利用
      • 协程和线程的区别
        • 协程是 线程级别的分时服用(比如一个线程里面有read数据的操作,这个操作的等待期间,可以做别的事情,这个就是协程的一个使用场景。)另一方面,由于操作系统是不知道有协程的存在的(协程只会切换用户空间的入口地址,线程不仅会切换用户空间的,还会切换内核空间中的入口地址) ,所以又被叫做“用户态线程”
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值