<table style="height: 30px; background-color: #afeeee; width: 1266px;" border="0"> |
<tr>
<td><span style="font-size: 16px;">一、线程介绍</td>
</tr></table>
线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位,一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
在同一个进程内的线程的数据是可以进行互相访问的。
线程的切换使用过上下文来实现的,比如有一本书,有a和b这两个人(两个线程)看,a看完之后记录当前看到那一页哪一行,然后交给b看,b看完之后记录当前看到了那一页哪一行,此时a又要看了,那么a就通过上次记录的值(上下文)直接找到上次看到了哪里,然后继续往下看。
线程中的5种状态:
?

各状态说明:
????????一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的
????????处于就绪状态的线程并不一定立即运行
????????线程运行过程中,可能由于各种原因进入阻塞状态:????????1>线程通过调用sleep方法进入睡眠状态;????????2>线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者;????????3>线程试图得到一个锁,而该锁正被其他线程持有;????????4>线程在等待某个触发条件;????????......???????????????????所谓阻塞状态是正在运行的线程没有运行结束,暂时让出
????????有两个原因会导致线程死亡:????????1) run方法正常退出而自然死亡,????????2) 一个未捕获的异常终止了run方法而使线程猝死。? ? ? ?为了确定线程在当前是否存活着(就是要么是可运行的,要么是被阻塞了),需要使用isAlive方法。如果是可运行或被阻塞,这个方法返回true,如果线程仍旧是new状态且不是可运行的, 或者线程死亡了,则返回false.
python中的多线程:
Python通过两个标准库thread和threading提供对线程的支持。thread提供了低级别的、原始的线程以及一个简单的锁,threading则弥补了其缺陷,所以线程模块使用threading就可以了。
多线程在Python内实则就是一个假象,为什么这么说呢,因为CPU的处理速度是很快的,所以我们看起来以一个线程在执行多个任务,每个任务的执行速度是非常之快的,利用上下文切换来快速的切换任务,以至于我们根本感觉不到。
但是频繁的使用上下文切换也是要耗费一定的资源,因为单线程在每次切换任务的时候需要保存当前任务的上下文。
什么时候用到多线程?
首先IO操作是不占用CPU的,只有计算的时候才会占用CPU(譬如1+1=2),Python中的多线程不适合CPU密集型的任务,适合IO密集型的任务(sockt server).
IO密集型(I/O bound):频繁网络传输、读取硬盘及其他IO设备称之为IO密集型,最简单的就是硬盘存取数据,IO操作并不会涉及到CPU。
计算密集型(CPU bound):程序大部分在做计算、逻辑判断、循环导致cpu占用率很高的情况,称之为计算密集型,比如说python程序中执行了一段代码1+1
,这就是在计算1+1的值
线程创建方法:
1.普通方法
<span style="color: #0000ff;">def<span style="color: #000000;"> task(n):
<span style="color: #0000ff;">print(<span style="color: #800000;">'<span style="color: #800000;">run task <span style="color: #800000;">'<span style="color: #000000;">,n)
<span style="color: #0000ff;">for i <span style="color: #0000ff;">in range(50):<span style="color: #008000;">#<span style="color: #008000;">启动50个线程
t=threading.Thread(target=task,args=(i,))<span style="color: #008000;">#<span style="color: #008000;">args参数是一个tuple
t.start()<span style="color: #008000;">#<span style="color: #008000;">启动线程
?2.类继承方法
==</span><span style="color: #0000ff;">def</span> run(self):<span style="color: #008000;">#</span><span style="color: #008000;">启动线程时候会允许run方法,这里重写父类run方法,可以自定义需要运行的task</span>
<span style="color: #0000ff;">print</span>(<span style="color: #800000;">'</span><span style="color: #800000;">start running ....</span><span style="color: #800000;">'</span><span style="color: #000000;">)
self._fun(self._agrs)
<span style="color: #0000ff;">def<span style="color: #000000;"> func(n):
<span style="color: #0000ff;">print<span style="color: #000000;">(n)
t
=Mythreading(func,1<span style="color: #000000;">)
t.start()<span style="color: #008000;">#<span style="color: #008000;">启动运行线程
<span style="color: #000000;">结果:
start running ....
1
?threading模块提供方法:
- start ? ? ? ? ? ?线程准备就绪,等待CPU调度
- setName ? ? ?为线程设置名称
- getName ? ? ?获取线程名称
- setDaemon ? 设置为守护线程(在start之前),默认为前台线程,设置为守护线程以后,如果主线程退出,守护线程无论执行完毕都会退出
- join ? ? ? ? ? ? ?等待线程执行结果,逐个执行每个线程,执行完毕后继续往下执行,该方法使得多线程变得无意义
- run ? ? ? ? ? ? ?线程被cpu调度后自动执行线程对象的run方法
- isAlive ? ?判断线程是否活跃
- threading.active_count 返回当前活跃的线程数量
- threading.current_thread 获取当前线程对象
使用list主线程阻塞,子现在并行执行demo:
(2(= i range(10=threading.Thread(target=fun,args=<span style="color: #0000ff;">for r <span style="color: #0000ff;">in thread_list:<span style="color: #008000;">#<span style="color: #008000;">循环每个线程,等待其结果,好处是,这样做所有线程启动之后一起join
r.join()
这样的好处在于,在启动线程后统一join,缩短了程序运行时间,并且提高运行效率。
关于python的GIL(Global Interpreter Lock)
首先需要明确的一点是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。就好比C++是一套语言(语法)标准,但是可以用不同的编译器来编译成可执行代码。有名的编译器例如GCC,INTEL C++,Visual C++等。Python也一样,同样一段代码可以通过CPython,PyPy,Psyco等不同的Python执行环境来执行。像其中的JPython就没有GIL。然而因为CPython是大部分环境下默认的Python执行环境。所以在很多人的概念里CPython就是Python,也就想当然的把GIL归结为Python语言的缺陷。所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL。
Python GIL其实是功能和性能之间权衡后的产物,它尤其存在的合理性,也有较难改变的客观因素,无论你启多少个线程,你有多少个cpu,Python在执行的时候在同一时刻只允许一个线程运行。
线程锁(互斥锁Mutex)
一个进程下可以启动多个线程,多个线程共享父进程的内存空间,也就意味着每个线程可以访问同一份数据,此时,如果2个线程同时要修改同一份数据,会出现什么状况?
=5
<span style="color: #0000ff;">def
<span style="color: #000000;"> fun():
<span style="color: #0000ff;">global<span style="color: #000000;"> NUM
NUM-=1<span style="color: #000000;">
time.sleep(2<span style="color: #000000;">)
<span style="color: #0000ff;">print<span style="color: #000000;">(NUM)
<span style="color: #0000ff;">for i <span style="color: #0000ff;">in range(5<span style="color: #000000;">):
t=threading.Thread(target=<span style="color: #000000;">fun)
t.start()
结果:
0
0
0
0
0
上述结果并不是我们想要的,去掉了sleep结果才是我们想要的,若是不去掉sleep呢,该怎么办?,此时我们可以加锁实现。
=5
<span style="color: #0000ff;">def
<span style="color: #000000;"> fun():
<span style="color: #0000ff;">global<span style="color: #000000;"> NUM
lock.acquire()<span style="color: #008000;">#<span style="color: #008000;">获取锁
NUM-=1<span style="color: #000000;">
time.sleep(2<span style="color: #000000;">)
<span style="color: #0000ff;">print<span style="color: #000000;">(NUM)
lock.release()<span style="color: #008000;">#<span style="color: #008000;">释放锁
lock=<span style="color: #000000;">threading.Lock()
<span style="color: #0000ff;">for i <span style="color: #0000ff;">in range(5<span style="color: #000000;">):
t=threading.Thread(target=<span style="color: #000000;">fun)
t.start()
RLock(递归锁)
递归锁,通俗来讲就是大锁里面再加小锁,有人可能会问,那我使用Lock不就完了吗,其实不然,想象一下,现在有两道门,一把锁对应一把钥匙,如果使用Lock,进去第一个门获取一把锁,在进去第二个人门又获取一把锁,然后要出来开锁时候(释放锁)程序还是用第一个门进来的钥匙,此时就会一直阻塞,那么RLock就解决了这样的问题场景。
demo
=threading.RLock()
(%(2(%(%1outdoor(<span style="color: #800000;">'<span style="color: #800000;">wd<span style="color: #800000;">'<span style="color: #000000;">)
<span style="color: #008000;">#<span style="color: #008000;">上述代码,若将l=threading.RLock()改为l=threading.Lock(),程序将一直阻塞。
Semaphore&BoundedSemaphore(信号量)
前面已经介绍过了互斥锁, 互斥锁同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据 ,比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去.
1.Semaphore和BoundedSemaphore使用方法一致
方法:
- acquire(blocking=True,timeout=None)
- release()
demo:
<span style="color: #0000ff;">def<span style="color: #000000;"> door(n):
sp.acquire()<span style="color: #008000;">#<span style="color: #008000;">获取一把锁,可设置超时时间
<span style="color: #0000ff;">print(<span style="color: #800000;">'<span style="color: #800000;">%d in the door<span style="color: #800000;">'%<span style="color: #000000;"> n)
time.sleep(1<span style="color: #000000;">)
<span style="color: #0000ff;">print(<span style="color: #800000;">'<span style="color: #800000;">%d out the door<span style="color: #800000;">'%<span style="color: #000000;"> n)
sp.release()<span style="color: #008000;">#<span style="color: #008000;">释放锁
sp=threading.Semaphore(3)<span style="color: #008000;">#<span style="color: #008000;">最多允许3个线程同时运行(获取到信号量)
<span style="color: #0000ff;">for i <span style="color: #0000ff;">in range(5<span style="color: #000000;">):
t=threading.Thread(target=door,))
t.start()<span style="color: #008000;">#<span style="color: #008000;">启动线程
<span style="color: #000000;">结果:
0 <span style="color: #0000ff;">in<span style="color: #000000;"> the door
1 <span style="color: #0000ff;">in<span style="color: #000000;"> the door
2 <span style="color: #0000ff;">in<span style="color: #000000;"> the door
1<span style="color: #000000;"> out the door
0 out the door
3 <span style="color: #0000ff;">in<span style="color: #000000;"> the door
4 <span style="color: #0000ff;">in<span style="color: #000000;"> the door
2<span style="color: #000000;"> out the door
4<span style="color: #000000;"> out the door
3 out the door
Events?
Event是线程间通信最间的机制之一:一个线程发送一个event信号,其他的线程则等待这个信号。用于主线程控制其他线程的执行。 Events 维护着一个flag,这个flag可以使用set()设置成True或者使用clear()重置为False,flag默认为False,而当flag为false时候,wait(timeout=s)则阻塞。
常用方法:
- Event.set():将标志设置为True
- Event.clear():清空标志位,设置为False
- Event.wait(timeout=s):等待(阻塞),直到标志位变成True
- Event.is_set():判断标志位是否被设置
通过Event来实现两个或多个线程间的交互,例如红绿灯,即起动一个线程做交通指挥灯,生成几个线程做车辆,车辆行驶按红灯停,绿灯行的规则。
demo:
count = count < 10(