一、基础概念
让步:线程的运行可能被抢占(中断),或暂时的被挂起(也叫睡眠),让其它的线程运行。
竞态条件:多个线程共同访问一片数据,则由于数据访问的顺序不 一样,有可能导致数据结果的不一致的问题。
全局解释器锁(GIL):全局解释器锁(GIL)是一个互斥锁,它可以 阻止多个本地线程一次执行Python字节码。这个锁主要是因为CPython的内存管理不是线程安全的。
执行流程
1、设置GIL
2、切换到一个线程去执行
3、运行
指定数量的字节码指令
线程主动让出控制(可以调用time.sleep(0))
6、把线程设置完睡眠状态
7、解锁GIL
8、再次重复以上步骤
对所有面向I/O的(会调用内建的操作系统C代码的)程序来说,GIL会在这个 I/O 调用之前被释放,以允许其它的线程在这个线程等待 I/O 的时候运行。如果某线程并未使用很多 I/O 操作,它会在自己的时间片内一直占用处理器(和 GIL)。也就是说,I/O 密集型的Python程序比计算密集型的程序更能充分利用多线程环境的好处。
线程的退出:
线程可以调用thread.exit()之类的退出函数,也可以使用Python退出进程的标准方法,如sys.exit()。但是不可以直接kill一个进程。
一、 Python的threading模块
Thread | 表示一个线程的执行的类 |
Lock | 互斥锁类 |
Rlock | 可重入锁,使用单线程可以再次获得已获得的锁。 |
Condition | 条件变量能让一个线程停下来,等待其他线程满足了某个条件 |
Event | 通用的条件变量。多线程可以等待某个事件的发生,在事件发生后,所有的线程都会被激活。 |
Semaphore | 为等待锁的线程 |
1、Thread类:
run() | 用以表示线程活动的方法 |
start() | 启动线程活动 |
join([time]) | 等待至线程中止。这阻塞调用线程直至线程的join() 方法被调用中止——正常退出或者抛出未处理 的异常-或者是可选的超时发生。 |
is_alive() | 返回线程是否活动的 |
name() | 设置/返回线程名 |
daemon() | 返回/设置线程的 daemon 标志,一定要在调用 start()函数前设置 |
创建线程的三种方法:
创建一个Thread的实例,传给它一个函数。
import threading
from time import sleep,time
loops = [4,2]
# 线程执行的逻辑函数
def loop(nloop,nsec):
print('start loop %s at:%s'%(nloop,time()))
sleep(nsec)
print('loop %s done at %s'%(nloop,time()))
def main():
print('starting at:',time())
threads = []
nloops = range(len(loops))
# 创建但不开始执行
for i in nloops:
t = threading.Thread(target=loop, args=(i, loops[i]))
threads.append(t)
print(threads)
# 开始执行
for i in nloops:
threads[i].start()
# 让主线程等待
for i in nloops:
threads[i].join()
print('all Done at:',time())
if __name__ == '__main__':
main()
创建一个Thread的实例,传给它一个可调用的类对象
from threading import Thread
from time import sleep,time
loops = [4,2]
class ThreadFunc():
def __init__(self,func,args,name=''):
self.name = name
self.func = func
self.args = args
def __call__(self):
#创建新线程的时候,thread对象会调用我们的threadfunc对象,
#这时会用到一个特殊函数__call__()
self.func(*self.args)
def loop(nloop,nsec):
print('start loop %s at:%s'%(nloop,time()))
sleep(nsec)
print('loop %s done at %s'%(nloop,time()))
def main():
print('starting at:',time())
threads = []
nloops = range(len(loops))
for i in nloops:
#创建ThreadFunc实例化的对象,创建所有线程
t = Thread(target=ThreadFunc(loop, (i, loops[i]), loop.__name__))
threads.append(t)
print(ThreadFunc(loop, (i, loops[i]), loop.__name__))
for i in nloops:
threads[i].start()
for i in nloops:
threads[i].join()
print('all Done at:',time())
if __name__ == '__main__':
main()
创建一个Thread 的实例,传递给它一个可调用的类对象
from threading import Thread
from time import sleep,time
loops = [4,2]
class MyThread(Thread):
def __init__(self,func,args,name=""):
super().__init__()
self.name = name
self.func = func
self.args = args
def getResult(self):
return self.res
def run(self):
print('strating',self.name,'at:',time())
self.res = self.func(*self.args)
print(self.name,'finished at:',time())
def loop(nloop,nsec):
print('start loop %s at:%s'%(nloop,time()))
sleep(nsec)
print('loop %s done at %s'%(nloop,time()))
def main():
print('starting at:',time())
threads = []
nloops = range(len(loops))
for i in nloops:
#创建ThreadFunc实例化的对象,创建所有线程
t = MyThread(loop, (i, loops[i]), loop.__name__)
threads.append(t)
for i in nloops:
threads[i].start()
for i in nloops:
threads[i].join()
print('all Done at:',time())
if __name__ == '__main__':
main()
start loop 0 at:1528184826.7092037
start loop 1 at:1528184826.710204
loop 1 done at 1528184828.7103183
loop 0 done at 1528184830.7094326
all Done at: 1528184830.7094326
2、Lock&Rlock类
原语锁定是一个同步原语,状态是锁定或未锁定。两个方法acquire()和release() 用于加锁和释放锁。RLock 可重入锁是一个类似于Lock对象的同步原语,但同一个线程可以多次调用。Lock不支持递归加锁,也就是说即便在同线程中,也必须等待锁释放。通常建议改 RLock,它会处理“owning thread”和“recursion level”状态,对于同线程的多次请求锁为,只累加计数器。每次调 release()将递减该计数器,直到0时释放锁,因此 acquire() 和 release() 必须要成对出现。
from time import sleep
from threading import current_thread,Thread,RLock
lock = RLock()
def show(i):
with lock:
print(current_thread().name,i)
sleep(0.1)
def tes():
with lock:
for i in range(3):
show(i)
for i in range(20):
Thread(target=tes).start()
print(lock)
Thread-1 0
<locked _thread.RLock object owner=5180 count=1 at 0x0000000001D705D0>
Thread-1 1
Thread-1 2
Thread-2 0
Thread-2 1
Thread-2 2
Thread-3 0
Thread-3 1
Thread-3 2
Thread-4 0
Thread-4 1
Thread-4 2
Thread-5 0
Thread-5 1
Thread-5 2
with的主要两个作用是:第一,代替实现了try、excepte、finally的功能,使其代码简洁了很多。第二,自动释放其拥有的资源,此资源包括打开的文件,获得的锁,申请的内存等等。
3、Event
事件用于在线程间通信。一个线程发出一个信号,其他一个或多个线程等待。Event通过一个内部标记来协调多线程运行。方法wait()阻塞线程执行,直到标记为Ture。 set() 将标记设为 True,clear() 更改标记为 False。isSet() 用于判断标记状态。
from threading import Event,Thread
def t_event():
e = Event()
return e
e = t_event()
def t():
for i in range(5):
print('start wait')
e.wait()
e.clear()
print(i)
Thread(target = t).start()
4、Condition
当需要线程关注特定的状态变化或事件的发生时使用这个锁定。可以认为,除了Lock带有的锁定池外,Condition还包含一个等待池,池中的线程处于状态图中的等待阻塞状态,直到另一个线程调用notify()/notifyAll()通知;得到通知后线程进入锁定池等待锁定。
acquire([timeout])/release(): | 调用关联的锁的相应方法 |
wait([timeout]) | 调用这个方法将使线程进入Condition的等待池等待通知,并释放锁。 使用前线程必须已获得锁定,否则将抛出异常。 |
notify() | 调用这个方法将从等待池挑选一个线程并通知,收到通知的线程将自动调用acquire()尝试获得锁定(进入锁定池);其他线程仍然在等待池中。调用这个方法不会释放锁定。使用前线程必须已获得锁定,否则将抛出异常。 |
notifyAll() | 调用这个方法将通知等待池中所有的线程,这些线程都将进入锁定池尝试获得锁定。调用这个方法不会释放锁定。使用前线程必须已获得锁定,否则将抛出异常。 |
from threading import Condition,current_thread,Thread
from time import sleep
con = Condition()
def tc1():
with con:
for i in range(5):
print(current_thread().name,i)
sleep(0.3)
if i == 3:
con.wait() #让出锁
def tc2():
with con:
for i in range(5):
print(current_thread().name,i)
sleep(0.1)
if i == 3:
con.notify()
Thread(target=tc1).start()
Thread(target=tc2).start()
Thread-1 0
Thread-1 1
Thread-1 2
Thread-1 3
Thread-2 0
Thread-2 1
Thread-2 2
Thread-2 3
Thread-2 4
Thread-1 4
只有获取锁的线程才能调用wait() 和 notify(),因此必须在锁释放前调用。当wait()释放锁后,其他线程也可进入wait 状态。notifyAll() 激活所有等待线程,让它们去抢锁然后完成后续执行。
5、生产者-消费者问题和Queue模块
queue(size) | 创建一个大小为size的Queue对象 |
qsize() | 返回队列的大小(由于在返回的时候,队列可能会被其它线程修改,所以这个值是近似值 |
empty() | 如果队列为空返回True,否则返回 False |
full() | 如果队列已满返回True,否则返回 False |
put(item,block=0) | 把item放到队列中,block参数为True,相当于队伍排满了,就在旁边等着,腾出位置再插进去;block为False,相当于队伍排满了,没位置了,直接投诉 |
get(block=0) | 从队列中取一个对象,如果给了block(不为 0),函数会一直阻塞到队列中有对象为止。block参数为True,相当于锅里没吃的了,就在旁边等着上新菜,上了新菜再直接端走;block为False,相当于锅里没货了,没耐心等新菜上锅,直接掀锅砸灶。 |
Queue模块可以用来进行线程间通讯,让各个线程之间共享数据。
from queue import Queue
from random import randint
from time import sleep,time
from threading import Thread
class MyThread(Thread):
def __init__(self,func,args,name=""):
super().__init__()
self.name = name
self.func = func
self.args = args
def getRusult(self):
return self.res
def run(self):
print('strating',self.name,'at:',time())
self.res = self.func(*self.args)
print(self.name,'finished at:',time())
#write()和read()函数分别用来把对象放入队列和消耗队列中的一个对象。
#在这里我们使用字符串‘xxx’来表示队列中的对象。
def writeQ(queue):
print('producing object for Q...')
queue.put('xxx',1)
print('size now',queue.qsize())
def readQ(queue):
queue.get(1)
print('consumed object from Q...',queue.qsize())
def writer(queue,loops):
#writer()函数只做一件事,就是一次往队列中放入一个对象,等待一会
#然后再做同样的事情。
for i in range(loops):
writeQ(queue)
sleep(1)
def reader(queue,loops):
#reader()函数只做一件事,就是一次从队列中取出一个对象,等待一会
#然后再做同样的事情。
for i in range(loops):
readQ(queue)
sleep(randint(2,5))
#设置有多少个线程要被运行
funcs = [writer,reader]
nfuncs = range(len(funcs))
def main():
nloops = randint(10,20)
q = Queue(32)
threads = []
for i in nfuncs:
t = MyThread(funcs[i],(q,nloops),funcs[i].__name__)
threads.append(t)
for i in nfuncs:
threads[i].start()
for i in nfuncs:
threads[i].join()
print(threads[i].getRusult())
print("all Done")
if __name__ == '__main__':
main()
strating writer at: 1528185472.6541495
producing object for Q...
size now 1
strating reader at: 1528185472.6541495
consumed object from Q... 0
producing object for Q...
size now 1
producing object for Q...
size now 2
consumed object from Q... 1
producing object for Q...
size now 2
producing object for Q...
size now 3
consumed object from Q... 2
producing object for Q...
size now 3
producing object for Q...
size now 4
producing object for Q...
size now 5
consumed object from Q... 4
producing object for Q...
size now 5
producing object for Q...
size now 6
producing object for Q...
size now 7
consumed object from Q... 6
producing object for Q...
size now 7
producing object for Q...
size now 8
producing object for Q...
size now 9
consumed object from Q... 8
writer finished at: 1528185486.6749518
None
consumed object from Q... 7
consumed object from Q... 6
consumed object from Q... 5
consumed object from Q... 4
consumed object from Q... 3
consumed object from Q... 2
consumed object from Q... 1
consumed object from Q... 0
reader finished at: 1528185511.6713815
None
all Done
常见问题解答:
1、进程与线程。线程与进程的区别是什么?
进程(有时被称为重量级进程)是程序的一次执行。每个进程都有自己的地址空间,内存,数据栈以及其它记录其运行轨迹的辅助数据。
线程(有时被称为轻量级进程)跟进程有些相似,不同的是,所有的线程运行在同一个进程中, 共享相同的运行环境。它们可以想像成是在主进程或“主线程”中并行运行的“迷你进程”。 http://www.ruanyifeng.com/blo…