python3_线程和进程

进程和线程

进程和线程概述

进程(Process)
是计算机中程序关于某数据集合上是一次活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础.
线程(Thread)
是轻量级进程(Lightweight Process,LWP),是程序执行流的最小单位.
一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成.

关系
程序是源代码文件, 程序被加载到内存中就是进程, 进程中存放着指令和数据,
进程是线程的容器, 进程中至少有一个线程作为程序的入口,就是线程的主线程
线程通常比进程快的多,进程间不可以随便的共享数据,同一个进程内的线程可以共享进程的资源,每个线程有独立的堆栈.

线程的状态
就绪(Ready),运行(Running),阻塞(Blocked),终止(Terminated)
阻塞 –(满足条件)–> 就绪 –(调度)–> 运行 –(完成)–> 终止
python启动一个解释器进程


threading.Thread类

def __init__(self, group=None, target=None,name=None,
    args=(), kwargs=None, *, daemon=None)
"""This constructor should always be called with keyword arguments. Arguments are:

    *group* should be None; reserved for future extension when a ThreadGroup class is implemented.
    *target* is the callable object to be invoked by the run() method. Defaults to None, meaning nothing is called.
    *name* is the thread name. By default, a unique name is constructed of the form "Thread-N" where N is a small decimal number.
    *args* is the argument tuple for the target invocation. Defaults to ().
    *kwargs* is a dictionary of keyword arguments for the target invocation. Defaults to {}.

    If a subclass overrides the constructor, it must make sure to invoke the base class constructor (Thread.__init__()) before doing anything else to the thread.
    """
    assert group is None, "group argument must be None for now"

assert expression \[,argument], 断言语句, 等价于raise-if-not
daemon=None: daemon=current_thread_daemon, main_thread_daemon=False
daemon=False: 主线程等待结束, 所有non_daemon线程都结束, 主线程才能退出
daemon=True: 主线程不等待结束, daemon线程(主线程的后台线程), 随主程序结束而退出

threading模块的一些方法

current_thread()
main_thread()
active_count()
enumerate():枚举活着的线程
get_ident(Tread_obj):获得线程id

Thread对象的一些属性和方法

.start():启动线程,并调用run函数
.run():不启动线程,仅调用target函数,默认执行一次后,del self._target, self._argrs, self._kwargs
.name:
.ident:
.daemon:
.is_alive():
.isDaemon():
.setDaemon(): 必须在线程start之前set
.join(): 将线程加入主线程执行, 作为主模块正常语句执行(必须执行完,才能执行下一句)

线程安全
程序的线程被执行,会产生不确定的结果,那么就称线程不安全, print函数就是线程不安全的, 例多线程中print(z, y, x),后面sep和end的可能先打印. 使用logging模块代替,安全的线程

    FORMAT = '%(asctime)-15s\t [%(threadName)s, %(thread)8d] %(message)s'
    logging.basicConfig(format=FORMAT, level=logging.INFO)
    logging.info("{} is running.".format(threading.current_thread().name))
    logging.warning("something")

threading.local类

实现一个全局对象(global),在不同线程中,其属性字典被重置为空,且使用互不干扰
一个对象,在不同的线程中拥有不用的属性


线程同步

线程间协同,通过某种技术,让一个线程访问某些数据时,其他线程不能访问这些数据,直到该线程完成对数据的操作

事件Event

是线程间通信机制中最简单的实现,使用一个内部的标记flag,通过flag的True或False的变化来进行操作.
threading.Event()

.set(),标记为True
clear(),标记为False
is_set(),判断标记是否为True
wait(timeout=None),等待,直到标记变为True,返回True,如果timeout,则返回False,None为无限等待.wait的个数没有限制,wait相对于time.sleep,它会更快的切换到其它线程,提高并发效率.

锁Lock和RLock

被锁住的语句块,只有获得锁的线程可以执行,一般加在,处理公共资源的地方,锁的语句块越小越好,不合理的加锁会降低多线程的效率,耗时长的语句块尽量不要加锁
threading.Lock();threading.Rlock()

.acquire(blocking: bool=True, timeout: float=-1) 获得锁就返回True,block_True,得不到锁就阻塞,timeout就返回False;block_False,得不到锁立马返回False,不阻塞,不能设置timeout. A timeout argument of -1 specifies an unbounded wait. -1表示永久阻塞
.release() 释放锁,释放unlocked锁,抛出RuntimeError

如果加锁后产生异常并被捕获,锁不被释放,就会产生死锁,程序就有可能阻塞
始终保证锁会被释放,常用:

  1. try…finally
  2. with上下文管理

RLock,可重入锁,是线程相关的锁
在同一线程中, 锁的获得没有次数限制,release次数要和acquire次数匹配,才能全部释放
同一个RLock在不同线程中,可以分别被获的,RLock锁的释放只针对当前线程的释放

状态Condition

Condition(lock=None),传入一个Lock或RLock对象,默认是RLock
实例方法:

acquire(blocking: bool=True, timeout: float=-1) 获得锁
release() 释放锁
wait(self, timeout=None) 等待获得消息,获得为True,超时为False,获得锁才能等待通知
notify(n=1) 通知n个等待的锁(当前或其他线程), 注意要先获得锁,才能发通知
notify_all() 通知所有等待的锁

condition使用了通知机制,效率较高,
使用上下文管理
threading.Condition 实现,广播通知

屏障Barrier

Python 3.2引入
Barrier(parties, action=None, timeout=None)
parties指定waiting线程数,达到数量一起通过,action为callable,通过时调用, timeout指定所有线程使用wait时的默认值
实例属性和方法:

.n_waiting: 当前屏障中等待的线程数,不是准确值
.parties: 即实例化时的parties
.broken: 是否处于broken状态,返回bool值
.wait(timeout=None): 将一个waiting加入Barrier,通过了就返回这个waiting相对在屏障中的索引range(parties),如果timeout, Barrier 被broken, 抛出BrokenBarrierError
.abort(): Place the barrier into a ‘broken’ state,所有屏障中waiting的和尝试使用wait()的线程都会抛出异常BrokenBarrierError
.reset(): 重置屏障,barrier中所有waiting抛出BrokenBarrierError,计数被重置为0

Barrier 使用
barrier应用: 所有指定的线程数量,被开启后,再执行下面的代码
从测试来看, Barrier会使最后一个进入等待的线程先执行

信号量semaphore

可以被获得指定次数的LOCK,(不同与RLock, 在不同线程共享锁的状态), 内部有一个计数器,每获得一次计数减一,当计数为0时就阻塞,有线程将锁释放后才能获得锁通过,释放一次,计数加一,释放多次,计数可能超过初始值
应用: 连接池,有限个连接,被重复使用
BoundedSemaphore 有界的信号量,release过多,抛出异常
Lock可以看做特殊的有界信号量,只能获得一次


Cpython 的多线程

CPython解释器的GIL锁(Global Interpreter Lock)使同一个进程中,即使是有多个cpu,也只有一个线程在运行,cup会分配给线程一定的时间片,如果时间片用完或者当前占用线程发生IO请求,线程就会发生切换

  1. 由于IO发生的线程切换,在IO密集型中经常发生,所有使CPython的”多线程”看起来像真正的多线程;
  2. 由于时间片用完而发生的切换,在cpu密集型中经常,这种情况下,由于线程的平凡切换,反而会使效率降低,所以要么用单线程,要么用多进程, 从测试中可以看出, 激活的线程往往被切换到的概率更大,就会导致第一个抢到的线程会更多的被切换执行

CPython,”多线程”并非真正的多线程,但是依然存在多线程访问同一个资源而发生混乱的情况,是因为线程间会发生切换,线程切换后,一些非原子操作,就可能发生冲突,
例如if num > 0; num += 1
num += 1 本身就不是 原子操作, 两句放在一起更不是原子操作了
要多线程(两个切换线程)间不发生干扰,就要使所有cpu操作都是原子性,产生原子操作的一种实现就是加锁

python中绝大多数内置数据结构的读写都是原子操作,由于GIL的存在,Python的内置数据类型在多线程编程的时候就变成安全的了,但是实际上它们不是线程安全的类型.
一个例子: 字典在遍历过程中,被其他线程修改,也会抛异常

time.sleep()和threading.Event().wait()都实现了暂停运行
如果是单纯的为了延迟,或者模拟IO,使用time.sleep()效率更高
如果为了让线程间更快的切换,更均衡的线程调度,就使用event.wait(), 从测试可以看出, 使用event.wait会使线程切换更均衡


多进程编程

multprocessing 模块
与threading模块的接口非常相似, 以下是一些不同的地方

.active_children()
.cup_count()
.current_process()

Process类的一些属性

.name:
.daemon:
.authkey: authentication key
.exitcode:
.pid:
.sentinel: A numeric handle of a system object which will become “ready” when the process ends.
.is_alive():
.terminate():终止进程
Process(target) target 必须是模块顶层属性, 不然抛错
线程/进程 start后才能join


进程间通信(IPC, Inter-Process Communication)

进程间通信必须序列化和反序列化
Manager() -> manager类,可以定义共享内存数据类型,manager.Array(),不能使用
Array() -> ctypes array 共享内存, 不能用append
Value() -> ctypes object 共享内存,value获得对象的值,定义查看array模块
Pipe([duplex]) -> (conn1, conn2) conn可以send和recv数据, duplex=False, 表示只能单向传输

Queue

Queue() 跨进程的Queue, 不能使用join方法
multiprocessing.JoinableQueue() 可以join()的multiprocess_Queue
queue.join() 表示阻塞,直到queue中元素被处理完
task_done() 和join配合使用, queue.get()后,任务处理完执行,执行queque.task_done,通知queue处理完一次
queue.close() 关闭queue
queue.join_thread() 将queue的后台线程join, 只在close()方法执行后才能使用, 如果当前进程不是queue的创建者, 那么进程退出时就会默认执行join_thread方法, queue.cancel_join_thread取消这一行为.

Pool(process=None)进程池:

apply(self,func,args=(),kwds={})
阻塞执行
apply_async(self,func,args=(),kwds={},callback=None,error_callback=None)
非阻塞执行,得到结果后执行回调,func要有返回值,传给callback函数执行
.close():关闭池,不接受新任务
.terminate(): 结束工作进程,不在处理未处理的任务
.join(): 主进程阻塞等待子进程退出,join()要在close()或terminate()之后执行

结束子进程两种实现:
1) daemon=True
2) queue中put sentinel, 当子进程get到sentinel, 就退出

iter(queue.get, None) 内建函数, 设置sentinel, 当遇到sentinel, 迭代退出
多进程应该处理不相干的过程,减少共享资源


concurrent 包

from concurrent.futures import ThreadPoolExecutor, ProcessPolExecutor
ThreadPoolExecutor(max_workers=1) -> 异步调用线程池执行器对象
ProcessPoolExecutor(max_workers=1) -> 异步调用的进程池执行器对象
executor.submit(fn, *args, **kwargs) -> 提交执行函数,返回future实例
shutdown(wait=True) -> 清理池
executor对象支持上下文管理
with ThreadPoolExecutor(1) as executor: pass

future类的一些方法:

.done() 调用是否被成功取消或执行
.cancelled() 调用是否被成功取消
.running() 调用是否正在执行且不能被取消
.cancel() 尝试取消调用, 不能取消返回False
.result(timeout=None) 取调用返回的结果,超时抛timeouterror
.exception(timeout=None) 取调用返回的异常,超时抛timeouterror

as_completed()接收一个可迭代的future实例的容器, 将完成的future迭代, future发生异常向外抛出并通过

from concurrent.futures import as_completed
for future in as_completed(futures):

协程coroutine

协程不是进程也不是线程,它是在用户空间完成调度并发处理的方式
协程减少了多线程中线程切换带来的消耗,协程不需要用锁机制
多cpu下,使用多进程和协程配合,既能进程并发又能发挥协程在单线程中的优势

asyncio库

asyncio.get_event_loop() 返回一个事件循环对象,是asyncio.BaseEventLoop的实例
实例的一些方法:

run_forever()
run_until_complete(future): 阻塞直到future完成, future对象使用asyncio.coroutine装饰器和生成器定义
wait(futures): futures中的元素依次yield
is_running()
is_closed()
close()

例子:

import asyncio

@asyncio.coroutine
def sleep(x):
    for i in range(3):
        print('sleep %s' % i)
        yield from asyncio.sleep(x)

loop = asyncio.get_event_loop()
print(loop.run_until_complete(sleep(1)))
tasks = [sleep(1), sleep(1)]
# loop.run_until_complete(asyncio.wait(tasks))
asyncio.wait(tasks) 
print('final')

新语法:

# TCP Server
async def handle(reader, writer):
    while True:
        data = await reader.read(1024)
        client = writer.get_extra_info('peername')
        writer.write(data)
        await writer.drain() # Flush the write buffer

loop = asyncio.get_event_loop()
ip = '127.0.0.1'
port = 9527
future = asyncio.start_server(handle, ip, port, loop=loop)
server = loop.run_until_complete(future) # accept socket
try:
    loop.run_forever() # 永远执行,直到调用stop()
except KeyboardInterrupt:
    pass
finally:
    server.close()
    loop.close()

并发

  • 队列,缓冲区,优先队列
  • 争抢,锁机制,可能有人长时间抢不到
  • 预处理,缓存常用
  • 并行,水平扩展
  • 提速,垂直扩展
  • 消息中间件,缓冲,常见的消息中间件: RabbitMQ, ActiveMQ(Apache),RocketMQ(阿里Apache),kafka(Apache)等

猜你喜欢

转载自blog.csdn.net/qq_33287645/article/details/81431488
今日推荐