python3 BIF里的并发与并行处理昝(IPC ITC)=>LTS

13 篇文章 0 订阅
6 篇文章 0 订阅

前言:

本文主要关注(进程,线程)调用,通信(合作),同步(竞争避免),退出。
主要讨论的是以下几个python3自带模块提供的并发、并行
1. _thread(thread in python2)=>底层线程模块
2. threading =>类似Java的Thread线程类,但更灵活的高级线程模块
3. os =>提供了较为底层的进程调用
4. multiprocessing =>试图避免GIL(Global Interpret Lock,python虚拟机的全局解释锁)对线程的并行锁定,和提供比起os模块的进程bif更好的可移植性

其中关于万恶的GIL,另外的的组织比如“听说”Google真在进行一个Unladen Swallow项目(卸下食道计划)以彻底抛弃GIL
现实情况是由于GIL机制的存在,线程不仅无法并行,而且切换的频率也不够高

0.不能并行的线程(用户级线程)

0.0. _thread

import _thread
'''
开启一个新线程,action是函数之类的可调用对象,同时以turple的形式传入参数(可选)
'''
_thread.start_new_thread(action,(2,))#函数
_thread.start_new_thread(obj.action)#类实例的方法

_thread.start_new_thread(action(2))#甚至是类似闭包调用
def action(i):
    def power():
        print(i**2)
    return power


'''
利用锁进行同步
'''
mutex=_thread.allocate_lock()
#手工加锁与释放
mutex.acquire()
#xxx,锁存的内容
mutex.release()

#更好更安全的方式是利用上下文管理器with控制锁,保证在发送异常时也能释放锁
with mutex:
    #xxx

'''
_thread没有join这样的等待退出机制,只能依靠time.sleep等待。
它是threading的底层基础,不建议直接使用
'''

0.1. threading

import threading
'''
开启一个新线程,使用方法与_thread类似(设置daemon=True保证子线程随父线程退出)
'''

threading.Thread(target=action,args=(2,)).start()#函数
threading.Thread(target=obj.action).start()#类实例的方法

threading.Thread(target=action(2)).start()#甚至是类似闭包调用
def action(i):
    def power():
        print(i**2)
    return power

'''
因为是threading.Thread是类对象,所以也可以通过覆盖它的run方法来调用(这正是java在做的事情,start调用run方法)
'''

'''
利用锁进行同步,与_thread类似
'''
lock=threading.Lock()
with lock:
    #xxx
'''
可以通过join延缓主线程先行退出退出
'''
thread1=threading.Thread(target=action,args=(2,))
thread1.start()
#xxxx
thread1.join()

0.2. 同步化队列queue

queue模块提供了几种支持线程同步化的队列(先进先出队列、优先级队列等)

import queue

'''
创建FIFO queue,队列中的对象可以是任意Python对象
'''
dataQueue=queue.Queue()

'''
入队列
'''
dataQueue.put(data)

'''
出队列
'''
try:
    data=dataQueue.get(block=False)
except queue.Empty:
    pass
else:
    #xxx

不管是哪一种线程API都没有提供主动结束子线程的方法,要实现正版功能可以通过线程共享全局变量的特性
在主线程中设置变量,子线程检测到变量为退出值,调用_thread.exit()

1.平台特定的OS进程

import os

1.0. Unix like系统下的fork ,exec组合

pid=os.fork()
if pid==0:#in child process
    os.execlp('python3','python3','child.py',args)#args为传入的参数
else:#in parent process
    #xxx

os.fork只有在Unix like系统(包括cygwin)才有效(os.exec家族平台中立)

1.0.1. os.exec家族

os.exec家族分需要额外“执行程序名参数”的v型,与直接如同与shell交互一样的l型
os.execv('python3',args)
os.execl('python3','python3',args)
后缀字母p表示使用系统PATH定位程序
os.execvp()
os.execlp()
后缀字母e表示在最后会添加一个字典传送给程序的shell变量
os.execve()
os.execle()

1.0.2. 匿名管道通信
pipein,pipeout=os.pipe()#从python3.4以后,出于安全考虑,pipe设置为不可继承。。。
#而且不知道为什么即使设置了文件描述符可继承,也不能与os.exec启动的子进程的程序通信
#匿名管道位于内存中
1.0.3. 命名管道(FIFO)通信

命名管道就是磁盘上的一个管道文件,在写完以后想要读到,需要刷新缓冲区

fifoname='pipefifo'
#xxx
if not os.path.exists(fifoname):
    os.mkfifo(fifoname)
'''
向命名管道写数据,不管用什么方式(文本或者二进制)打开,只要两边约定好即可
'''
pipeout=os.open(fifoname,os.O_WRONLY)
#或者pipeout=open(fifoname,'w')
os.write(pipeout,'Hello')
#或者pipeout.write('Hello'+'\n')#最后的额外的换行是为了让readline读出
pipeout.flush()#需要及时刷新缓存区

'''
读数据
'''
pipein=open(fifoname)
pipein.readline()[:-1]#去掉额外添加的换行符

注意:命名管道在写之前读会出现Broken pipe的错误,
但如果没有读写两端同时打开,先开的一端会堵塞

1.1. 有点过时的,已经平台中立的os.spawn家族

应该首先考虑的是os.subprocess而不是os.spawn
但os.spawn仍然可以用,而且是平台中立的
os.spawnv
os.spawnve
与os.exec家族相同命名,但有些只限于Unix like

import sys
pypath=sys.executable
os.spawnv(os.P_NOWAIT,pypath,('python','child.py',args))

windows or unix
os.P_NOWAIT or os.P_NOWAITO都表示立即返回进程ID
os.P_WAIT 在新进程结束前不返回,返回进程退出代码或’-signal’
only windows
os.P_DETACH or os.P_OVERPLAY
新进程从调用的进程控制台分离,os.P_OVERPLAY会替换当前进程

1.2. 依赖于shell的调用

1.2.0. os.system

与c语言里的system()一样

1.2.1.os.popen

与os.system相同,但自动连接了stdin,stdout

with os.popen('python hello-in.py','w') as pipe:#对stdin进行写操作,with对文件描述符进行自动管理
    pipe.wite('xxxx\n') 

with os.popen('python hello-in.py') as pipe:#对stdin进行读操作
    pipe.read()
1.2.1.os.startfile

效果等同于start a file in cmd of windows

1.3.推荐使用subprocess启动程序

import subprocess

subprocess.Popen()#用法类似os.popen
pipe=subprocess.Popen('test.py',stdout=subprocess.PIPE)
sts =subprocess.call("test1.py" + " myarg", shell=True)

暂时不太了解,以后再说

2.压轴的multiprocessing

有线程般可移植性的进程模块,但依然有一些比如共享内存无法再Windows下实现,如果你发现突然出现无法pickle化的错误,有可能就是windows不支持。。。

from muliprocessing import Process,Pipe,Lock,Event,Queue

'''
开启新进程,与threading完全一样
'''
child=Process(target=action,args=(arg1,))
child.start()
'''
锁
'''
lock=Lock()

'''
同步化队列
'''
data_queue=Queue()

'''
管道通信,比os.pipe更高级,不需要手动转码
'''
pipein,pipeout=Pipe()
pipein.send('Hello')
msg=pipeout.recv()
'''
等待子进程退出
'''
child.join()#这样也是保证没有僵尸进程
#或者初始化时,daemon=True,保证子进程随父进程退出而退出
child=Process(target=action,args=(i,),daemon=True)
'''
自带退出方法terminate,但是On Unix this is done using the SIGTERM signal;
on Windows TerminateProcess() is used.
Note that exit handlers and finally clauses, etc., 
will not be executed.需要注意它不会终结子进程,
而会使他们变成孤立进程,同时可能破坏管道等IPC使其它未终结底层进程也无法使用。。。不建议用。。。
'''
child.terminate()
'''
与线程的手动结束一样,可以检测一个变量(共享内存实现),父进程控制这个变量来手动结束子进程
子进程检测到变量的结束信号:
'''
sys.exit()

2.1. 共享内存通信

(通过指针共享,这是IPC中最快的方式,接近于直接访问内存)

from multiprocessing import Value,Array
import ctypes

'''
Value是创建一个c 类型的变量,Array是创建一个c 类型的数组
Value Array提供进程同步管理

c 类型既可以用类型代码表示,也可以用ctype表示,为了直观表示,我这里导入了
ctypes,用ctypes里的类型表示
'''
AnInt=Value(ctypes.c_int,2)#创建一个c类型的整数变量AnInt,值为2
UnicodeStr=Array(ctypes.c_ubyte,1024)#或者在第二个位置传入一个构造器,range(10)
#通过共同访问这些变量,实现共享内存通信

2.2 同步方式(这里提到的都来自于threading模块,threading那里就不另写了)

2.2.1进程级锁

lock=Lock()

2.2.2condition(带测试条件的锁)
2.2.3RLOCK(一旦获得过,下一次访问就可以直接访问,不必再等待)
2.2.4Event
eventA=Event()

eventA.wait()#等待事件发生(True)
eventA.set()#事件设为发生(设为True)
eventA.clear()#事件结束(设为False)
2.2.5Namespace,dict,list,Barrier,BoundedSemaphore

2.3有效的并发–pool(线程池)

2.2.5semaphore(信号量)

对数量有限但不唯一的独占资源进行有效的访问控制

3.以上通用的IPC方式

3.0. 套接字通信

可以跨网络通信,不受特定平台、特定机器限制,应该算作最强大的IPC工具

from socket import socket,AF_INET,SOCK_STREAM
port=8888
host='localhost'
'''
选择可靠传输的TCP协议
'''
def server():
    sock=socket(AF_INET,SOCK)
    sock.bind(('',port))
    sock.listen(5)
    while True:
        conn,addr=sock.accept()
        data=conn.recv(1024)#读取字节数据
        reply='server get %s'%data#这里不能使用str.format方法格式化字符串,因为data是字节数据
        conn.send(reply.encode())

def client():
    sock=socket(AF_INET,SOCK)
    sock.connect((host,port))
    sock.send('hello'.encode())
    reply=sock.recv(1024)
    sock.close()

3.1. 信号(signal)通信

import sys,signal

signal.signal(signum,onSignal)#信号编号与信号处理器

我考虑可以
通过pickle等序列化手段存储读取数据,通过信号通知到自提点自提
这样来传递消息

3.2. 其他

3.2.0. mmap模块提供的共享内存

效率比利用ctypes的内存访问要慢至少一个数量级,但使用比较方便

import mmap

4 subprocess

Python主要的进程模块儿之一,它与multiprocessing的
区别在于
http://stackoverflow.com/questions/13606867/what-is-the-difference-between-multiprocessing-and-subprocess

The subprocess module lets you run and control other programs. Anything you can start with the command line on the computer, can be run and controlled with this module. Use this to integrate external programs into your Python code.

The multiprocessing module lets you divide tasks written in python over multiple processes to help improve performance. It provides an API very similar to the threading module; it provides methods to share data across the processes it creates, and makes the task of managing multiple processes to run Python code (much) easier. In other words, multiprocessing lets you take advantage of multiple processes to get your tasks done faster by executing code in parallel.
4.1 subprocess 注意事项

通过重定向Popen实例的stdin,stdout,stderr来实现不同进程间流的交换
一般通过read/write家族进行读写(print不知道为什么有问题), 注意flush,而且写都是bytes-like,但可以通过
设置Popen实例的encoding属性,使得读方直接得到str

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值