Python time、进程、线程、协程(异步IO)

Python既支持多进程,又支持多线程

sys.exit([status])

抛出异常(SystemExit)并退出,如异常被捕获则不退出

  • status默认为0,表示正常退出。其他int表示异常退出
  • 传入非int内容则exit status为1,其内容可被SystemExit输出
import sys
try:
    sys.exit("err_msg")
    print(1)
except SystemExit as err:
    print(2, err)
finally:
    print(3)

os._exit(status)

必须传入int,执行后立刻强制退出

time

import time
time.sleep(1)#休眠当前线程1秒
time.time()#当前时间

多进程 multiprocessing

通常在CPU密集型时使用多进程

  • os.getpid() 查看当前进程pid
  • os.getppid() 查看父进程pid
  • os.fork()只能在Mac和Linux下调用,需要跨平台应使用multiprocessingsubprocess包,其中multiprocessing启动的子进程会重新加载父进程加载过的模块
    • windows下的multiprocessing,实际Process实例化代码需要放在if __name__ == '__mian__'内执行
孤儿进程

父进程先于子进程退出,此时子进程成为孤儿进程,被系统进程收养。系统进程变成其父进程,不会产生僵尸进程

僵尸进程

子进程先于父进程退出,此时子进程处于僵尸状态,保留其状态信息(如依然占用进程号pid)。

可通过以下方法消除僵尸进程:

  • kill 掉僵尸进程的主进程,如主进程自行调用exit或通过命令行进行kill操作等。此时僵尸进程的父进程会变为系统进程(init进程),init进程自然会调用wait或者waitpid清除僵尸进程
  • os.wait()
    调用后保持阻塞,直到找到一个僵尸进程将它销毁,返回一个元组,包含其 pid 和退出状态指示
  • os.waitpid(pid, options)
    类似os.wait(),等待销毁指定pid的进程
  • signal.signal(signal.SIGCHLD, handle_func)
    监听每个子进程退出时的触发信号,通过handle_func回调处理。
    通常在handle_func中执行上述三个方法之一。
  • signal.signal(signal.SIGCHLD, signal.SIG_IGN)
    此处的SIG_IGN并非默认的忽略,通过此方法子进程会在结束时直接被回收,不再能被wait/waitpid捕获。
Signal

信号,用于进程之间通知其他进程的事件,不用于传递具体数据
通过signal.signal捕获信号后,可覆盖其默认行为。

常见Linux信号:

名称 含义 默认行为
SIGCHLD 子进程退出或中断 忽略
SIGINT 键盘按下Ctrl+C 终止
SIGTERM kill pidkill -15 pidkill -SIGTERM 终止
SIGKILL kill -9 pidkill -SIGKILL 终止,不可捕获或覆盖
SIGHUT 用户终端结束,如关闭命令行 终止

多线程 threading

通常在IO密集型时使用多线程

  • 注意,线程之间其实只有主线程和非主线程,没有父子关系。所谓的子线程中再创建线程,依然和创建它的子线程之间是平级的。
  • exit()只会终止当前线程。因其本质是抛出一个错误,而错误只会打断当前线程。os._exit(0)可以真正终止整个python进程,但会绕过Python的清理和资源回收机制,不建议使用。
  • Ctrl + C只会终止主线程
全局解释器锁 GIL

只有CPython和ruby中有GIL,主要源于历史问题。GIL会保证同一进程下,同一时间仅有一个线程持有Python解释器的控制权。
由于GIL的存在,CPython的多线程无法有效利用多核CPU,其同一个进程下的多线程无法正常并行,因此最大并行数即CPU数。在CPU密集型任务时显著影响性能,建议使用多进程代替。在IO密集型任务时,因大部分时间在等待IO,无需争抢GIL,因此影响不大。

双核CPU中,两个CPU密集型线程的互相影响(红色为线程等待获取GIL的时间)

  • 通过threading.current_thread()可以查看当前所在的线程,主线程实例的名字叫MainThread,子线程的名字在创建时通过name指定,未指定则自动命名为Thread-1Thread-2……

  • 通过threading.Thread()创建线程,通过线程实例的start方法开始线程

  • start前调用setDaemon或实例化时传入daemon项设置此子线程是否为守护线程

    • 默认False,子线程保持执行自己的任务,不受主线程或其他线程结束的影响。
    • 设为True,所有非守护线程(包括主线程)结束时,守护线程会自动结束。
      其他线程结束通知守护线程的过程中也需要时间,因此守护线程在这个时间间隔内也可能执行少量动作。
      非主线程中新建的线程,默认会继承当前线程的daemon
      因为守护线程随时会结束,因此守护线程中不适合执行一些容易导致数据损坏的操作,比如文件的写入。守护线程最典型的应用就是GC(垃圾收集器)。
  • start后调用join()进行线程同步,其参数timeout默认为None,在该子线程完成前会阻塞当前线程(不一定是主线程)后续代码执行。如为数字,则最多只阻塞timeout的时间

import threading
import time

def td():
    time.sleep(1)
    print('当前线程名字是:' + threading.current_thread().name)
    time.sleep(1)

if __name__ == '__main__':
    td()
    s_time = time.time()
    print(s_time)

    print('这是主线程:' + threading.current_thread().name)
    tdg_list = []

    for i in range(5):
        t = threading.Thread(target=td)
        tdg_list.append(t)

    for t in tdg_list:
        # t.setDaemon(True) # 设置为守护线程(后台线程)  所有非守护线程(包括主线程)结束时t线程也结束
        t.start()
        t.join() # 在t完成前阻塞当前线程(注意不一定是主线程)

    print('主线程结束了!', threading.current_thread().name)
    print('一共用时:', time.time()-s_time)
  • 多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响。而多线程中,所有变量都由所有线程共享,为了防止多个线程同时操作一个数据引起的错误,使用threading.Lock()保证某段代码每次只有一个线程在执行
    只有一个线程能通过.acquire()获取锁,并继续执行代码,其他线程调用.acquire()时会继续等待直到锁被.release()释放。
#通过try...finally保证锁能被释放
lock=threading.Lock()
lock.acquire() # 锁定线程
try:
  change_it(n)
finally:
  lock.release() # 解锁

# 或使用`with`自动加解锁
instance_lock = threading.Lock()
with instance_lock:
    instance = RedisClient(host='127.0.0.1',port=6379,password='',db=1)    
  • 由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁,导致多个线程全部挂起,既不能执行,也无法结束。

  • 通过threading.Event()创建事件(标志默认为False)也可以用于阻塞线程,区别在于当event标志为True时所有阻塞同时畅通

    • event.wait(timeout=None):调用该方法的线程会被阻塞,如果设置了timeout参数,超时后,线程会停止阻塞继续执行;
    • event.set():将event的标志设置为True,调用wait方法的所有线程将被唤醒;
    • event.clear():将event的标志设置为False,调用wait方法的所有线程将被阻塞;
    • event.isSet():判断event的标志是否为True。
import threading
import time
def do(event):
    print('start')
    event.wait()#红灯,所有线程执行都这里都在等待
    print('end')
 
event_obj = threading.Event()#创建一个事件
for i in range(3):#创建10个线程
    t= threading.Thread(target=do,args=(event_obj,))
    t.start()
 
time.sleep(3)
 
event_obj.clear()#让灯变红,默认也是红的,阻塞所有线程运行
data= input('请输入要:')
print(event_obj.isSet())#False
if data =='True':
    event_obj.set()#变绿灯

#start
#start
#start
#请输入要:True
#end
#end
#end
ThreadLocal

一个ThreadLocal实例虽然是全局变量,但每个线程都只能读写自己线程的独立副本,互不干扰也不用管理锁的问题,ThreadLocal内部会处理。

local_school = threading.local()
local_school.student = name
print(local_school.student)
退出线程

默认情况下,线程 start 后当执行完成时,自动结束。可通过 线程实例.isAlive()判断线程是否存活。
但当线程执行一个耗时任务,或持续任务时,需要提前结束

  • 使用线程实例._stop()强制结束线程(不推荐)
    线程正在执行的任务可能会受影响,如正在存取的文件未正确关闭,造成数据丢失、内存溢出等。
  • 使用全局变量作为 flag
# 主线程
flag = True

# 子线程
global flag
while flag:
    pass
  • 使用 threading.Event 对象关闭子线程
    Event 是一个线程间安全的全局 flag,通过控制 event 对象状态,来实现线程间的通讯与同步
    • event.isSet(): 返回event的状态值;
    • event.wait(): 如果 event.isSet()==False将阻塞线程,event建立后默认为False;可用于控制线程暂停
    • event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;
    • event.clear(): 恢复event的状态值为False。
from threading import Thread,Event
import time

event=Event()

def light():
    print('红灯正亮着')
    time.sleep(3)
    event.set() #绿灯亮

def car(name):
    print('车%s正在等绿灯' %name)
    event.wait() #等灯绿 此时event为False,直到event.set()将其值设置为True,才会继续运行.
    print('车%s通行' %name)

if __name__ == '__main__':
    # 红绿灯
    light=Thread(target=light)
    light.start()

    # car1
    car1=Thread(target=car,args=(1,))
    car1.start()
    # car2
    car2=Thread(target=car,args=(2,))
    car2.start()

Master-Worker 模式

通常多任务的程序可以采用Master-Worker模式,Master负责分配任务,Worker负责执行任务

  • 如果用多进程实现Master-Worker,主进程就是Master,其他进程就是Worker。
    这种方式比较稳定,一个子进程挂了不会影响其他进程,但在windows下开销特别大(因windows对多进程优化较差)
  • 如果用多线程实现Master-Worker,主线程就是Master,其他线程就是Worker。
    比多进程快(尤其是windows),但是一个线程挂了会导致整个程序挂掉
master-worker

计算密集型与IO密集型

  • 计算密集型任务(CPU密集型)
    特点是要进行大量的计算,消耗CPU资源,比如计算圆周率、对视频进行高清解码等等,全靠CPU的运算能力。任务越多,花在任务切换的时间就越多,CPU效率就越低,所以,计算密集型任务同时进行的数量不应大于CPU的核心数。
    对于计算密集型任务,最好用C语言编写。

  • IO密集型
    涉及到网络、磁盘IO的任务都是IO密集型任务,CPU消耗很少,任务的大部分时间都在等待IO操作完成(如等待数据库返回信息)。对于IO密集型任务,任务越多,CPU效率越高,但也有一个限度。常见的大部分任务都是IO密集型任务,比如Web应用。
    对于IO密集型任务,用运行速度极快的C语言替换用Python这样运行速度极低的脚本语言,对运行效率提升不大,开发效率却低,因此脚本语言更为合适。

分布式进程

把繁重任务分布到多台机器的环境下

你可能感兴趣的:(Python time、进程、线程、协程(异步IO))