python——线程
Posted 小帅哥,快来玩呀
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python——线程相关的知识,希望对你有一定的参考价值。
线程
线程属于轻量级的进程
进程和线程的关系
线程的特点
1)轻型实体
2)独立调度和分派的基本单位。
在多线程OS中,线程是能独立运行的基本单位,因而也是独立调度和分派的基本单位。由于线程很“轻”,故线程的切换非常迅速且开销小(在同一进程中的)。3)共享进程资源。
线程在同一进程中的各个线程,都可以共享该进程所拥有的资源,这首先表现在:所有线程都具有相同的进程id,这意味着,线程可以访问该进程的每一个内存资源;此外,还可以访问进程所拥有的已打开文件、定时器、信号量机构等。由于同一个进程内的线程共享内存和文件,所以线程之间互相通信不必调用内核。
4)可并发执行。
在一个进程中的多个线程之间,可以并发执行,甚至允许在一个进程中所有线程都能并发执行;同样,不同进程中的线程也能并发执行,充分利用和发挥了处理机与外围设备并行工作的能力。
全局解释器锁GIL
Python代码的执行由Python虚拟机(也叫解释器主循环)来控制。Python在设计之初就考虑到要在主循环中,同时只有一个线程在执行。虽然 Python 解释器中可以“运行”多个线程,但在任意时刻只有一个线程在解释器中运行。
对Python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。
在多线程环境中,Python 虚拟机按以下方式执行:
a、设置 GIL;
b、切换到一个线程去运行;
c、运行指定数量的字节码指令或者线程主动让出控制(可以调用 time.sleep(0));
d、把线程设置为睡眠状态;
e、解锁 GIL;
d、再次重复以上所有步骤。
在调用外部代码(如 C/C++扩展函数)的时候,GIL将会被锁定,直到这个函数结束为止(由于在这期间没有Python的字节码被运行,所以不会做线程切换)编写扩展的程序员可以主动解锁GIL。
threading模块
线程的创建Threading.Thread类
from threading import Thread import time def sayhi(name): time.sleep(2) print(\'%s say hello\' %name) if __name__ == \'__main__\': t=Thread(target=sayhi,args=(\'egon\',)) t.start() print(\'主线程\')
守护线程
无论是进程还是线程,都遵循:守护xx会等待主xx运行完毕后被销毁。需要强调的是:运行完毕并非终止运行
1.对主进程来说,运行完毕指的是主进程代码运行完毕
2.对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕
import time from threading import Thread def func(): print(\'开始执行子线程\') time.sleep(3) print(\'子线程执行完毕\') t = Thread(target=func) t.setDaemon(True) # 进程设置守护进程 是一个属性 daemon = True t.start() t2 = Thread(target=func) t2.start() t2.join() # 等待t2结束 结果 \'\'\' 开始执行子线程 开始执行子线程 子线程执行完毕 子线程执行完毕 \'\'\'
锁
GIL 不是锁数据 而是锁线程
在多线程中 特殊情况 仍然要加锁 对数据
import time from threading import Thread from threading import Lock def func(): global n time.sleep(2) lock.acquire() temp = n # 从进程中获取n time.sleep(0.01) n = temp-1 # 得到结果,再存储回进程 lock.release() n = 100 lock = Lock() t_lst = [] for i in range(100): t = Thread(target=func) t.start() t_lst.append(t) [t.join() for t in t_lst] print(n)
死锁与递归锁
进程也有死锁与递归锁,在进程那里忘记说了,放到这里一切说了额
所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程
from threading import Lock as Lock import time mutexA=Lock() mutexA.acquire() mutexA.acquire() print(123) mutexA.release() mutexA.release()
解决方法,递归锁,在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。
这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁:
from threading import RLock as Lock import time mutexA=Lock() mutexA.acquire() mutexA.acquire() print(123) mutexA.release() mutexA.release()
信号量
同进程的一样
Semaphore管理一个内置的计数器,
每当调用acquire()时内置计数器-1;
调用release() 时内置计数器+1;
计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()。
实例:(同时只有5个线程可以获得semaphore,即可以限制最大连接数为5):
信号量 和 线程池 有什么区别?
相同点 在信号量acquire之后,和线程池一样 同时在执行的只能有n个
不同点
开的线程数不一样 线程池来说 一共就只开5个线程 信号量有几个任务就开几个线程
对有信号量限制的程序来说 可以同时执行很多线程么?
实际上 信号量并不影响线程或者进程的并发,只是在加锁的阶段进行流量限制
事件
同进程的一样
线程的一个关键特性是每个线程都是独立运行且状态不可预测。如果程序中的其 他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就会变得非常棘手。为了解决这些问题,我们需要使用threading库中的Event对象。 对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。在 初始情况下,Event对象中的信号标志被设置为假。如果有线程等待一个Event对象, 而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行
event.isSet():返回event的状态值; event.wait():如果 event.isSet()==False将阻塞线程; event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度; event.clear():恢复event的状态值为False。
条件
使得线程等待,只有满足某条件时,才释放n个线程
import threading def run(n): con.acquire() con.wait() # 等着 print("run the thread: %s" % n) con.release() if __name__ == \'__main__\': con = threading.Condition() # 条件 = 锁 + wait的功能 for i in range(10): t = threading.Thread(target=run, args=(i,)) t.start() while True: inp = input(\'>>>\') if inp == \'q\': break con.acquire() # condition中的锁 是 递归锁 if inp == \'all\': con.notify_all() else: con.notify(int(inp)) # 传递信号 notify(1) --> 可以放行一个线程 con.release()
定时器
from threading import Timer def hello(): print("hello, world") while True: # 每隔一段时间要开启一个线程 t = Timer(10, hello) # 定时开启一个线程,执行一个任务 # 定时 : 多久之后 单位是s # 要执行的任务 :函数名
线程队列
queue队列 :使用import queue,用法与进程Queue一样
queue is especially useful in threaded programming when information must be exchanged safely between multiple threads.
- class
queue.
Queue
(maxsize=0) #先进先出
import queue q=queue.Queue() q.put(\'first\') q.put(\'second\') q.put(\'third\') print(q.get()) print(q.get()) print(q.get()) \'\'\' 结果(先进先出): first second third \'\'\'
class queue.
LifoQueue
(maxsize=0) #last in fisrt out
import queue q=queue.LifoQueue() q.put(\'first\') q.put(\'second\') q.put(\'third\') print(q.get()) print(q.get()) print(q.get()) \'\'\' 结果(后进先出): third second first \'\'\'
class queue.
PriorityQueue
(maxsize=0) #存储数据时可设置优先级的队列
值越小越优先,值相同就asc码小的先出
import queue q=queue.PriorityQueue() #put进入一个元组,元组的第一个元素是优先级(通常是数字,也可以是非数字之间的比较),数字越小优先级越高 q.put((20,\'a\')) q.put((10,\'b\')) q.put((30,\'c\')) print(q.get()) print(q.get()) print(q.get()) \'\'\' 结果(数字越小优先级越高,优先级高的优先出队): (10, \'b\') (20, \'a\') (30, \'c\') \'\'\'
Python标准模块--concurrent.futures
#1 介绍 concurrent.futures模块提供了高度封装的异步调用接口 ThreadPoolExecutor:线程池,提供异步调用 ProcessPoolExecutor: 进程池,提供异步调用 Both implement the same interface, which is defined by the abstract Executor class. #2 基本方法 #submit(fn, *args, **kwargs) 异步提交任务 #map(func, *iterables, timeout=None, chunksize=1) 取代for循环submit的操作 #shutdown(wait=True) 相当于进程池的pool.close()+pool.join()操作 wait=True,等待池内所有任务执行完毕回收完资源后才继续 wait=False,立即返回,并不会等待池内的任务执行完毕 但不管wait参数为何值,整个程序都会等到所有任务执行完毕 submit和map必须在shutdown之前 #result(timeout=None) 取得结果 #add_done_callback(fn) 回调函数
以上是关于python——线程的主要内容,如果未能解决你的问题,请参考以下文章
newCacheThreadPool()newFixedThreadPool()newScheduledThreadPool()newSingleThreadExecutor()自定义线程池(代码片段