概念
进程(Process):程序的运行过程
线程(Thread):程序执行过程中的最小单元
协程(Coroutine):是单线程下的并发,又称微线程,纤程
关系:
线程属于进程,一个进程可以有多个线程,但至少有一个线程。
资源分配给进程,同一进程的所有线程共享该进程的所有资源。
CPU分给线程,即真正在CPU上运行的是线程。
windows查看电脑CPU核数和进程数
cmd -> 输入 wmic -> 输入 cpu get *
NumberOfCores为核数,NumberOfLogicalProcessors为线程数
cpu get NumberOfCores
cpu get NumberOfLogicalProcessors
python查看字节码:dis模块
dis.dis(func)
选择
进程切换代价要高于线程
对于耗费CPU的操作,多进程优于多线程(计算,图像处理等)
对于IO操作来说,多线程优于多进程(网络通信,延时等),或者可以使用协程
多进程+协程,充分利用CPU资源
—————————————————————————-多线程—————————————————————————
内容转载整理自:@StormZhu
GIL全局解释器锁
GIL 全称 gloabl interpreter lock (全局解释器锁) ,是在实现Python解析器(CPython)时所引入的一个概念,而CPython是大部分环境下默认的Python执行环境。
GIL遵循的原则:“一个线程运行 Python ,而其他 N 个睡眠或者等待 I/O.”(即保证同一时刻只有一个线程对共享资源进行存取)
GIL事实上是会释放的,python中有两种多任务处理:
协同式多任务处理:一个线程无论何时开始睡眠或等待网络 I/O,就会释放GIL锁
抢占式多任务处理:如果一个线程不间断地在 Python 2 中运行 1000 字节码指令,或者不间断地在 Python 3 运行15 毫秒,那么它便会放弃 GIL,而其他线程可以运行
初识多线程
1).threading库
构造方法:threading.Thread()
import threading # 导入线程包
thread1 = threading.Thread(target=get_detail_html, args=("",))
2).守护(daemon)线程
在主线程执行完成之后,子线程居然还在背后默默执行,在很多情况下,这都是不好的。解决办法就是使用守护线程。
设置一个线程是守护线程,就说明这不是一个很重要的线程,对于这样的线程,只要主线程运行结束,就会直接退出。
thread1.setDaemon(True)
3).继承threading.Thread
重载线程类的的run()方法
多线程通信
1).全局变量
没有加锁无法同步的情况下,存在一定的冲突隐患
2).消息队列–queue模块
queue进行了很好的封装,在放值和取值的时候时线程安全的。
三种队列:
class queue.Queue(maxsize = 0): 构造一个FIFO(First In First Out)队列,maxsize可以限制队列的大小。
如果队列的大小达到了队列的上限,就会加锁,加入就会阻塞,直到队列的内容被消费掉。
maxsize的值小于等于0,那么队列的尺寸就是无限制的
class queue.LifoQueue(maxsize = 0): 构造一个LIFO(Last In First Out)队列
class PriorityQueue(maxsize = 0):优先级最低的先出去,优先级最低的一般使用sorted(list(entries))[0]。
Entries 通常是这个形式的元组: (priority number, data)
线程间同步之Lock ,RLock
1).互斥锁Lock
基本用法:
import threading
lock = threading.Lock()
lock.acquire() # 获取锁
# dosomething…… # 临界区的代码只能被同时只能被一个线程运行
lock.release() # 释放锁
缺陷:
添加锁之后,会影响程序性能。
可能引起”死锁”。
迭代死锁。一个线程“迭代”请求同一个资源,还未 release 就再次acquire
互相调用死锁。两个线程中都会调用相同的资源,互相等待对方结束的情况。
2).可重入锁RLock
为解决同一线程种不能多次请求同一资源的问题,避免迭代死锁
lock = threading.RLock()
def dosomething(lock):
lock.acquire()
# do something
lock.release()
lock.acquire()
dosomething(lock)
lock.release()
线程间同步之条件变量Condition
举例:线程1有两个任务A和B,而线程2有C和D,线程启动以后会出现(A,B),(C,D)的情况,而不是想要的(A,C),(B,D)
import threading
# 可传入一个互斥锁或者可重入锁
cond = threading.Condition()
cond.wait()
cond.notify()
cond.notifyAll()
# 只能是一个线程执行过了wait(),在被阻塞过程中,另一个线程执行了notify()才可以,所以要注意线程启动顺序
# condition有两层锁, 一把底层锁会在线程调用了wait方法的时候释放,上面的锁会在每次调用wait的时候分配一把并放入到cond的等待队列中,等到notify方法的唤醒
条件变量提供了对复杂线程同步问题的支持。
条件变量也是使用互斥锁实现的,主要是两层锁结构
线程间同步之信号量Semaphore
信号量semaphore 是用于控制进入数量的锁。
Semaphore(value=1) # value设置是内部维护的计数器的大小,默认为1.
每当调用acquire()时,内置计数器-1,直到为0的时候阻塞
每当调用release()时,内置计数器+1,并让某个线程的acquire()从阻塞变为不阻塞
信号量 semaphore 可以控制同时运行执行的线程数量。
信号量 semaphore 内部维护了一个条件变量和一个计数器。
ThreadPoolExecutor线程池
在线程池中预先创建好一个较为优化的数量的线程,让过来的任务立刻能够使用,可以控制线程数,同时避免在切换线程的时候,需要切换上下文环境而造成cpu的大量开销
它提供了ThreadPoolExecutor和ProcessPoolExecutor两个类,实现了对threading和multiprocessing的进一步抽象。
不仅可以帮我们自动调度线程,还可以做到:
主线程可以获取某一个线程(或者任务的)的状态,以及返回值。
当一个线程完成的时候,主线程能够立即知道。
让多线程和多进程的编码接口一致。
1).ThreadPoolExecutor
提交任务
通过submit(fn, *args, **kwargs)异步提交执行的函数到线程池中,submit函数立即返回,不阻塞
map(func, *iterables, timeout=None, chunksize=1)可以取代for循环submit的操作,输出顺序和urls列表的顺序相同
submit()和fas_completed()的组合比map()更灵活,submit()可以处理不同的调用函数和参数,而map只能处理同一个(partial函数不考虑的话)可调用对象
executor.shutdown(wait=True)
关闭进程池的显式调用,wait为True则等待所有任务结束及释放完资源以后才返回值,使用with语句与这方式一致;
为False时则是先返回结果再继续等到所有任务执行完毕和释放完资源以后才退出;
不管 wait 的值是什么,整个 Python 程序将等到所有待执行的期程完成执行后才退出
回调函数
add_done_callback(fn)
同步调用
提交完任务后,就在原地等待任务执行完毕,拿到结果后再执行下一行代码,导致程序是串行
future1 = executor.submit(fib, (3)).result()
fuc(future1)
future2 = executor.submit(fib, (2)).result()
fuc(future2)
异步调用
提交完任务后,不用原地等待任务执行完毕
future1 = executor.submit(fib, (3)).add_done_callback(fuc)
future2 = executor.submit(fib, (2)).add_done_callback(fuc)
2).Future对象
concurrent.futures.Future
Future 实例由 Executor.submit() 创建
3).模块对象
wait(fs, timeout=None, return_when=ALL_COMPLETED):阻塞主线程
return_when
FIRST_COMPLETED
函数将在第一个任务执行结束或取消时返回。
FIRST_EXCEPTION
函数将在任意一个任务因引发异常而结束时返回。没有任何异常时相当于 ALL_COMPLETED
ALL_COMPLETED
函数将在所有可等待对象结束或取消时返回。
as_complete(fs, timeout=None)
as_completed()方法是一个生成器,在没有任务完成的时候,会阻塞。
在有某个任务完成时,会yield这个任务,就能执行for循环下面的语句,然后继续阻塞住,循环到所有的任务结束
4).实例
from concurrent.futures import ThreadPoolExecutor, as_completed, wait
def fib(n):
if n<=2:
return 1
return fib(n-1)+fib(n-2)
executor = ThreadPoolExecutor(max_workers=2)
future1 = executor.submit(fib, (3))
future2 = executor.submit(fib, (2))
print(future1.done()) # done方法用于判定某个任务是否完成
print(future2.cancel()) # cancel方法用于取消某个任务,该任务没有放入线程池中才能取消成功
print(future1.result()) # result方法可以获取task的执行结果
if __name__ == "__main__":
with ThreadPoolExecutor(3) as executor:
# all_task = [executor.submit(fib, (num)) for num in range(15, 20)]
# for future in as_completed(all_task):
# data = future.result()
# print("exe result: {}".format(data))
for data in executor.map(fib, range(15,20)):
print("exe result: {}".format(data))