Day12- Python基础12 线程、GIL、Lock锁、RLock锁、Semaphore锁、同步条件event
http://www.cnblogs.com/yuanchenqi/articles/6248025.html 博客地址
本节内容:
1:进程和线程的说明
2:线程的两种调用方式
3:threading.thread的实例方法
4:python的GIL
5:互斥锁Lock
6:递归锁Rlock
7:Semaphore锁
8:同步条件event
9:队列
1:进程和线程的说明
进程一般由程序、数据集、进程控制块三部分组成。 我们编写的程序用来描述进程要完成哪些功能以及如何完成; 数据集则是程序在执行过程中所需要使用的资源; 进程控制块用来记录进程的外部特征,描述进程的执行变化过程,系统可以利用它来控制和管理进程,它是系统感知进程存在的唯一标志。 线程也叫轻量级进程,它是一个基本的CPU执行单元,也是程序执行过程中的最小单元,由线程ID、程序计数器、寄存器集合和堆栈共同组成。
进程和线程的大白话
进程:本质上就是程序运行的实例
进程和进程之前的数据是不能共享的。
线程和线程之间可以共享。
比如说你开一个qq,qq就是qq程序执行的实例,是进程。而qq里面的功能就是各个线程。
2.线程的两种调用方式
threading 模块建立在thread 模块之上。thread模块以低级、原始的方式来处理和控制线程,而threading 模块通过对thread进行二次封装,
提供了更方便的api来处理线程。
直接调用:
import threading import time def sayhi(num): #定义每个线程要运行的函数 print("running on number:%s" %num) time.sleep(3) if __name__ == '__main__': t1 = threading.Thread(target=sayhi,args=(1,)) #生成一个线程实例 t2 = threading.Thread(target=sayhi,args=(2,)) #生成另一个线程实例 t1.start() #启动线程 t2.start() #启动另一个线程 print(t1.getName()) #获取线程名 print(t2.getName())
继承式调用:(ps没多大用,一般直接调用就好)
import threading import time class MyThread(threading.Thread): def __init__(self,num): threading.Thread.__init__(self) self.num = num def run(self):#定义每个线程要运行的函数 print("running on number:%s" %self.num) time.sleep(3) if __name__ == '__main__': t1 = MyThread(1) t2 = MyThread(2) t1.start() t2.start() print("ending......")
3.threading.thread的实例方法
join方法:
当线程对象采用join的时候,必须先执行完当前的子线程,主线程才可以工作;而子线程之间没有这个关系。
join():在子线程完成运行之前,这个子线程的父线程将一直被阻塞。
1 import threading 2 import time 3 4 def music(): 5 print("begin to listen %s"%time.ctime()) 6 time.sleep(5) 7 print("end to listen %s" % time.ctime()) 8 9 def game(): 10 print("begin to game %s"%time.ctime()) 11 time.sleep(3) 12 print("end to game %s" % time.ctime()) 13 14 15 if __name__ == "__main__": 16 t1 = threading.Thread(target=music) ##开启了一个线程 17 18 19 t2 = threading.Thread(target=game) ##开启第二个线程 20 t2.start() 21 t1.start() 22 t1.join() ##t1不执行完,主线程不能走 23 t2.join() ## 24 print("end.......") 25 26 ##输出 bengin game 和 listen同时打印出来,过了三秒end game再打印出来,等再过2秒打印出end listen 和 end.... 27 # begin to game Sat Mar 17 22:20:37 2018 28 # begin to listen Sat Mar 17 22:20:37 2018 29 # end to game Sat Mar 17 22:20:40 2018 30 # end to listen Sat Mar 17 22:20:42 2018 31 # end.......
setDaemon 守护线程
1 import threading 2 import time 3 4 def f1(i): 5 time.sleep(1) 6 print(i) 7 8 if __name__ == '__main__': 9 for i in range(5): 10 t = threading.Thread(target=f1, args=(i,)) 11 t.setDaemon(True) ##守护主线程,。只要主线程结束,就陪着一起退出 12 t.start() 13 14 print('start') # 主线程不等待子线程 15 16 ##输出 17 #start
setDaemon(True): 将线程声明为守护线程,必须在start() 方法调用之前设置, 如果不设置为守护线程程序会被无限挂起。这个方法基本和join是相反的。 当我们 在程序运行中,执行一个主线程,如果主线程又创建一个子线程,主线程和子线程 就分兵两路,分别运行,那么当主线程完成 想退出时,会检验子线程是否完成。如 果子线程未完成,则主线程会等待子线程完成后再退出。但是有时候我们需要的是 只要主线程 完成了,不管子线程是否完成,都要和主线程一起退出,这时就可以 用setDaemon方法啦。
其它方法
除此之外,自己还可以为线程自定义名字,通过 t = threading.Thread(target=f1, args=(i,), name='mythread{}'.format(i)) 中的name参数,除此之外,Thread还有一下一些方法
# run(): 线程被cpu调度后自动执行线程对象的run方法 t.start() :启动线程活动。 t.getName() : 获取线程的名称 t.setName() : 设置线程的名称 t.name : 获取或设置线程的名称 t.is_alive() : 判断线程是否为激活状态 t.isAlive() :判断线程是否为激活状态 t.isDaemon() : 判断是否为守护线程 threading模块提供的一些方法: # threading.currentThread(): 返回当前的线程变量。 # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。 # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果
4.python的GIL
In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)
上面的核心意思就是,无论你启多少个线程,你有多少个cpu, Python在执行的时候会淡定的在同一时刻只允许一个线程运行
GIL是存在于pythonC解释器中,你要想在python中使用多线程并行 那就别想了。java中是不存在这个锁的,但是用户需要繁琐的加锁解锁,保障线程的安全。
任务的区分:
对于IO密集型的任务,python的多线程是有意义的
对于计算密集型的任务;python的多线程不推荐,可以采用多进程+协程
并发&并行
并发:是指系统具有处理多个任务(动作)的能力 ;通俗的说就是cpu能够进行切换任务,就说具有了并发。 并行:是值系统具有同时处理多个任务(动作)的能力 并行是并发的一个子集
同步 & 异步
现象:如上个博文所说的socket,当你等待用户或服务端的数据recv 时,就一直阻塞在那里。这叫同步 同步:当进程执行到一个IO(等待外部数据的时候),等 ---就是同步 异步:当进程执行到一个IO(等待外部数据的时候),不等 ---就是异步
5.同步锁(也叫互斥锁)
###开100个线程进行 累加或累减 import threading import time def sub(): global num temp = num time.sleep(0.0001) num = temp -1 # num -= 1 num = 100 l = [] for i in range(100): t = threading.Thread(target=sub) t.start() l.append(t) for i in l : i.join() print(num) # 输出: 我想要的是0 怎么给我输出了78了? # 78
分析:加的time.sleep()的区别就是cpu遇见了io阻塞,马上就进行切换。
大家都处理一个数据,而这个数据在进行切换的时候,数据还没执行完就进行切换,导致了同一时刻,不同的线程拿到了同一个数据。
解决办法:加同步锁
import threading import time def sub(): global num lock.acquire() ##将下面的代码锁起来,在我锁的过程谁都不能对下面的数据进行操作 temp = num time.sleep(1) ##1秒意味着 100秒才可以出结果 num = temp -1 lock.release() ##释放锁 # num -= 1 num = 100 l = [] lock = threading.Lock() ###创建了一把锁 for i in range(100): t = threading.Thread(target=sub) t.start() l.append(t) for i in l : i.join() print(num) # 输出: # 0
def sub():
global num
print('ok')
time.sleep(1)
print('ok2')
lock.acquire() ##将下面的代码锁起来,在我锁的过程谁都不能对下面的数据进行操作
temp = num
time.sleep(0.01) ##1秒意味着 100秒才可以出结果
num = temp -1
lock.release() ##释放锁
##我串行的部分就是 lock的三行部分 其他的还是 并行的,
6.死锁锁、递归锁
同步锁的缺点:
在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁,因为系统判断这部分资源都正在使用,所有这两个线程在无外力作用下将一直等待下去。下面是一个死锁的例子:
死锁实例:
1 import threading 2 import time 3 4 class MyThread(threading.Thread): 5 def actionA(self): 6 A.acquire() 7 print(self.name,"GotA",time.ctime()) ###self 是一个线程对象,打印出线程名字 8 time.sleep(2) 9 10 B.acquire() 11 print(self.name, "GotB", time.ctime()) 12 time.sleep(2) 13 B.release() ##释放B锁 14 A.release() ##释放A锁 15 16 17 def actionB(self): 18 B.acquire() 19 print(self.name, "GotB", time.ctime()) ###self 是一个线程对象,打印出线程名字 20 time.sleep(1) 21 22 A.acquire() 23 print(self.name, "GotA", time.ctime()) 24 A.release() ##释放A锁 25 B.release() ##释放B锁 26 27 def run(self): 28 self.actionA() 29 self.actionB() 30 31 if __name__ == '__main__': 32 33 ##创建两把锁 34 A = threading.Lock() 35 B = threading.Lock() 36 L = [] 37 for i in range(5): 38 t = MyThread() 39 t.start() 40 L.append(t) 41 42 for i in L: 43 i.join() 44 45 print('ending.........') 46 47 48 ##5个线程一起走都要获取到lock a ,线程1拿到了a 其他四个都要等待,线程1 释放 a锁才能进来, 49 #线程1 50 51 # 输出: 52 # Thread-1 GotA Sun Mar 18 12:07:12 2018 53 # Thread-1 GotB Sun Mar 18 12:07:14 2018 54 # Thread-1 GotB Sun Mar 18 12:07:16 2018 55 # Thread-2 GotA Sun Mar 18 12:07:16 2018
解决办法 递归锁(Rlock):
由于线程是共享同一份内存的,所以如果操作同一份数据,很容易造成冲突,这时候就可以为线程加上一个锁了,这里我们使用Rlock,而不使用Lock,因为Lock如果多次获取锁的时候会出错,而RLock允许在同一线程中被多次acquire,但是需要用n次的release才能真正释放所占用的琐,一个线程获取了锁在释放之前,其他线程只有等待。
import threading import time class MyThread(threading.Thread): def actionA(self): rlock.acquire() print(self.name,"GotA",time.ctime()) ###self 是一个线程对象,打印出线程名字 time.sleep(2) rlock.acquire() print(self.name, "GotB", time.ctime()) time.sleep(2) rlock.release() ##释放B锁 rlock.release() ##释放A锁 def actionB(self): rlock.acquire() print(self.name, "GotB", time.ctime()) ###self 是一个线程对象,打印出线程名字 time.sleep(1) rlock.acquire() print(self.name, "GotA", time.ctime()) rlock.release() ##释放A锁 rlock.release() ##释放B锁 def run(self): self.actionA() self.actionB() if __name__ == '__main__': ##创建两把锁 # A = threading.Lock() # B = threading.Lock() L = [] ##创建递归锁 rlock = threading.RLock() for i in range(5): t = MyThread() t.start() L.append(t) for i in L: i.join() print('ending.........') ##5个线程一起走都要获取到lock a ,线程1拿到了a 其他四个都要等待,线程1 释放 a锁才能进来, #线程1 # 输出: # Thread-1 GotA Sun Mar 18 12:17:52 2018 # Thread-1 GotB Sun Mar 18 12:17:54 2018 # Thread-1 GotB Sun Mar 18 12:17:56 2018 # Thread-1 GotA Sun Mar 18 12:17:57 2018 # Thread-3 GotA Sun Mar 18 12:17:57 2018 # Thread-3 GotB Sun Mar 18 12:17:59 2018 # Thread-3 GotB Sun Mar 18 12:18:01 2018 # Thread-3 GotA Sun Mar 18 12:18:02 2018 # Thread-5 GotA Sun Mar 18 12:18:02 2018 # Thread-5 GotB Sun Mar 18 12:18:04 2018 # Thread-2 GotA Sun Mar 18 12:18:06 2018 # Thread-2 GotB Sun Mar 18 12:18:08 2018 # Thread-2 GotB Sun Mar 18 12:18:10 2018 # Thread-2 GotA Sun Mar 18 12:18:11 2018 # Thread-5 GotB Sun Mar 18 12:18:11 2018 # Thread-5 GotA Sun Mar 18 12:18:12 2018 # Thread-4 GotA Sun Mar 18 12:18:12 2018 # Thread-4 GotB Sun Mar 18 12:18:14 2018 # Thread-4 GotB Sun Mar 18 12:18:16 2018 # Thread-4 GotA Sun Mar 18 12:18:17 2018 # ending.........
递归锁的,内部就是维护 了一个计算器默认是0,当有人用rlock 就+1,直到rlock = 0 才能继续,让线程竞争锁,谁抢到谁执行。
7:Semaphore锁
Semaphore锁也是锁的一种,类似于停车场 ,停车场一次可以停3辆车,当第三辆车来了之后,只能等待前面三辆车离开大于等于1辆 才能进入。
实例:
import threading import time class MyThread(threading.Thread): def run(self): if more.acquire(): ##加锁 当停车场小于三的时候,车进入 time.sleep(3) print(self.name) more.release() ## 解锁,车离开 允许后面的车进来 more = threading.Semaphore(3) ##默认停车场三辆 l = [] for i in range(23): t = MyThread() t.start() l.append(t) for i in l: i.join() print('end........') # 输出: 没三秒输出三个线程 # Thread-2 # Thread-1 # Thread-3 # Thread-4 # Thread-5 # Thread-6
8:同步条件event
在之前不管是加锁还是其他的,线程都是处于竞争的,谁抢到cpu就执行谁。而达不到一个相互协调工作的情况。
event的原理很简单:就是线程之间共同围绕着一个标志位,当线程A没有set标志位的时候,线程B阻塞住。当线程A set标志位的时候,让B线程执行。
达到一个同步的效果:
就三个方法记住:
# a client thread can wait for the flag to be set
event.wait()
# a server thread can set or reset it
event.set()
event.clear()
import threading,time class Boss(threading.Thread): def run(self): print("BOSS:今晚大家都要加班到22:00。") print(event.isSet()) event.set() time.sleep(5) print("BOSS:<22:00>可以下班了。") print(event.isSet()) event.set() class Worker(threading.Thread): def run(self): event.wait() ##boss没set 就阻塞住,一但boss set开始执行 print("Worker:哎……命苦啊!") time.sleep(1) event.clear() event.wait() print("Worker:OhYeah!") if __name__=="__main__": event=threading.Event() threads=[] for i in range(5): ##创建5个work threads.append(Worker()) threads.append(Boss()) ##创建一个Boss for t in threads: t.start() for t in threads: t.join() # 输出: # BOSS:今晚大家都要加班到22:00。 # False # Worker:哎……命苦啊! # Worker:哎……命苦啊! # Worker:哎……命苦啊! # Worker:哎……命苦啊! # Worker:哎……命苦啊! # BOSS:<22:00>可以下班了。 # False # Worker:OhYeah! # Worker:OhYeah! # Worker:OhYeah! # Worker:OhYeah! # Worker:OhYeah!
9.队列Queue
队列是一种数据结构,不懂什么是数据结构?
数据结构就是帮你存储数据的一种格式,比如说列表 是按一个个索引值去存储数据,而字典是通过哈希给你存储数据。
为什么要有队列?
之所以会出现队列是因为遇见了多线程,它保证了数据的安全。
创建队列对象
import queue q = queue.Queue() q = queue.Queue(5) ##就表示创建了一个5个格子的队列
创建一个队列对象就类似创建了这样的一个格子,接下来我们就要往里面添值。
添值
q.put(12) q.put('hi') q.put([1,3,4])
获取值
while 1 : data = q.get() print(data)
输出:
12 hi [1, 3, 4]
完整代码
import queue q = queue.Queue() q.put(12) q.put('hi') q.put([1,3,4]) while 1 : data = q.get() print(data)
我们说创建了一个格子,那么开始是怎么放置值的?
先进先出队列 queue.Queue() 后进先出队列 queue.LifoQueue() 按优先级队列 queue.PriorityQueue()
1 import queue 2 3 # q = queue.Queue() 4 q = queue.LifoQueue() 5 # q = queue.PriorityQueue() 6 7 q.put(12) 8 q.put('hi') 9 q.put([1,3,4]) 10 11 while 1 : 12 data = q.get() 13 print(data) 14 15 # 输出: 16 # [1, 3, 4] 17 # hi 18 # 12
数据越小优先级越高
1 import queue 2 3 q = queue.PriorityQueue() 4 5 q.put([2,'hi']) 6 q.put([4,34]) 7 q.put([6,[1,3,4]]) 8 9 while 1 : 10 data = q.get() 11 # print(data) 12 print(data[1]) 13 14 # 输出: 默认输出的是一个列表 15 # [2, 'hi'] 16 # [4, 34] 17 # [6, [1, 3, 4]] 18 19 # hi 20 # 34 21 # [1, 3, 4]
其他的一些方法
q.qsize() 返回队列的大小 q.empty() 如果队列为空,返回True,反之False q.full() 如果队列满了,返回True,反之False q.full 与 maxsize 大小对应 q.get([block[, timeout]]) 获取队列,timeout等待时间 q.get_nowait() 相当q.get(False) ##不等待程序往队列放值,直接报错 非阻塞 q.put(item) 写入队列,timeout等待时间 q.put_nowait(item) 相当q.put(item, False) ##当队列满了,阻塞住程序,直到队列get出去,此方法表示不等待,直接报错 q.task_done() 在完成一项工作之后,q.task_done() 函数向任务已经完成的队列发送一个信号 q.join() 实际上意味着等到队列为空,再执行别的操作