Python自动化 【第九篇】:Python基础-线程、进程及python GIL全局解释器锁
本节内容:
- 进程与线程区别
- 线程
- a) 语法
- b) join
- c) 线程锁之Lock\Rlock\信号量
- d) 将线程变为守护进程
- e) Event事件
- f) queue队列
- g) 生产者消费者模型
3. python GIL全局解释器锁
1. 进程与线程区别
线程:是操作系统能够进行运算和调度的最小单位,是一堆指令的集合。线程被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。线程就是cpu执行时所需要的一堆上下文关系。
进程:以一个整体的形式暴露给操作系统管理,里面包含对各种资源的调用,内存的管理,网络接口的调用等,对各种资源管理的集合就可称为 进程。进程要操作cpu, 必须先创建一个线程。
进程和线程的区别:
- 线程共享内存空间,进程的内存是独立的
- 线程共用数据,进程数据独立
- 同一个进程的线程之间可以直接交流,两个进程必须通过中间代理实现通信
- 新线程容易创建,新进程需要克隆父进程
- 一个线程可以控制和操作同一进程里的其他线程, 进程只能操作子进程
修改主线程有可能影响到其他线程的行为,对父进程修改不会影响子进程。
2. 线程(threading模块)
a) 语法
先写一个简单的线程:
import threading import time def run(n): print("task" ,n) time.sleep(2) t1 = threading.Thread(target=run, args=("t1",)) t2 = threading.Thread(target=run, args=("t2",)) t1.start() t2.start() print(t1.getName) #获取线程名 print(t2.getName)
继承式调用:
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()
继承式调用
启动多个线程:
import threading import time def run(n): print("task" ,n) time.sleep(2) for i in range(50): t = threading.Thread(target=run, args=("t_%s" % i,)) t.start()
启用多线程
b) join
join & Daemon用法:
默认情况主线程不会等子线程执行完毕,但是join可以做到
import threading import time def run(n): print("task" ,n) time.sleep(2) print("task done ", n) start_time = time.time() t_objs = [] for i in range(50): t = threading.Thread(target=run, args=("t_%s" % i,)) t.start() t_objs.append(t) for t in t_objs: t.join() print("cost_time:",time.time()-start_time)
join用法
主线程是程序本身
threading.current_thread()和threading.active_count()用法:
import threading import time def run(n): print("task" ,n) time.sleep(2) print("task done ", n,threading.current_thread()) start_time = time.time() t_objs = [] for i in range(50): t = threading.Thread(target=run, args=("t_%s" % i,)) t.start() t_objs.append(t) # for t in t_objs: # t.join() print("===all threads has finished", threading.current_thread(), threading.active_count()) print("cost_time:",time.time()-start_time)
threading.current_thread()和threading.active_count()
c) 信号量:
threading.BoundedSemaphore(n)
互斥锁 同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据 。
threading.BoundedSamphore(n)
d) 把子线程变成守护线程setDaemod()方法:
#!/usr/bin/env python # -*- coding:utf-8 -*- # Author: zhoujunlong import threading import time def run(n): print("task" ,n) time.sleep(2) print("task done ", n,threading.current_thread()) start_time = time.time() for i in range(50): t = threading.Thread(target=run, args=("t_%s" % i,)) t.setDaemon(True) # 把当前线程设置为守护线程 , 在start之前 t.start() print("===all threads has finished", threading.current_thread(), threading.active_count()) print("cost_time:",time.time()-start_time)
set Daemod()
e) 事件:
事件是一个简单地同步对象
event = threading.event()
event.wait() 等待标志位被设定
event.set() 设置标志位
event.clear() 清除标志位
event.is_set() 判断标志位是否设定
通过Event来实现两个或多个线程间的交互,下面是一个红绿灯的例子,即起动一个线程做交通指挥灯,生成几个线程做车辆,车辆行驶按红灯停,绿灯行的规则。
#!/usr/bin/env python # -*- coding:utf-8 -*- # Author: zhoujunlong import threading import time event = threading.Event() def lighter(): count = 0 event.set() while True: if 10>=count > 5:#change_to_red_light event.clear() # 清空标志位 print("\033[41;1m红灯了\033[0m") elif count > 10: event.set() #change_to_green_light count = 0 else:print("\033[42;1m绿灯了\033[0m") time.sleep(1) count += 1 def car(name): while True: if event.is_set():#代表绿灯 print("[%s] running..." % name) time.sleep(1) else: print("[%s] sees red light, waiting" % name) event.wait() print("\033[34;1m[%s] 绿灯了,gogogo\033[0m"%name) light = threading.Thread(target=lighter) light.start() car1 = threading.Thread(target=car,args=("QQ",)) car2 = threading.Thread(target=car,args=("TT",)) car1.start() car2.start()
示例1-红绿灯
这里还有一个event使用的例子,员工进公司门要刷卡, 我们这里设置一个线程是“门”, 再设置几个线程为“员工”,员工看到门没打开,就刷卡,刷完卡,门开了,员工就可以通过。
import threading import time import random def door(): door_open_time_counter = 0 while True: if door_swiping_event.is_set(): print("\033[32;1mdoor opening....\033[0m") door_open_time_counter +=1 else: print("\033[31;1mdoor closed...., swipe to open.\033[0m") door_open_time_counter = 0 #清空计时器 door_swiping_event.wait() if door_open_time_counter > 3:#门开了已经3s了,该关了 door_swiping_event.clear() time.sleep(0.5) def staff(n): print("staff [%s] is comming..." % n ) while True: if door_swiping_event.is_set(): print("\033[34;1mdoor is opened, passing.....\033[0m") break else: print("staff [%s] sees door got closed, swipping the card....." % n) print(door_swiping_event.set()) door_swiping_event.set() print("after set ",door_swiping_event.set()) time.sleep(0.5) door_swiping_event = threading.Event() #设置事件 door_thread = threading.Thread(target=door) door_thread.start() for i in range(5): p = threading.Thread(target=staff,args=(i,)) time.sleep(random.randrange(3)) p.start()
示例2-员工进门刷卡
f) queue(队列)
class queue.Queue(maxsize=0) #先入先出
class queue.LifoQueue(maxsize=0) #先入先出 #last in first out
class queue.PriorityQueue(maxsize=0) #存储数据时可设置优先级的队列
- 实现程序的解耦
- 提高运行效率
>>> import queue >>> q = queue.Queue >>> q.put("disk1") >>> q.put("disk2") >>> q.put("disk3") >>q.qsize() 3 >>>q.get() 'disk1' >>>q.get() 'disk2' >>>q.get() 'disk3' >>>q.get_nowait()
queue
抛异常,不会卡住
maxsize()方法:
>>>q = queue.Queue(maxsize=3) >>>q.put(1) >>>q.put(2) >>>q.put(3) >>>q.put(4)
maxsize()方法
再put时会卡住
Lifo
import queue q = queue.LifoQueue() q.put(1) q.put(2) q.put(3) print(q.get()) print(q.get()) print(q.get())
Lifo
程序输出:
3 2 1
程序输出
Priority方法:
import queue q = queue.PriorityQueue() q.put(("-1a1")) q.put(("5, a2")) q.put(("2, a3")) print(q.get()) print(q.get()) print(q.get())
Priority方法
程序输出:
-1, a1 2, a3 5, a2
程序输出
Queue.task_done() 以下为解释:
Indicate that a formerly enqueued task is complete. Used by queue consumer threads. For each get() used to fetch a task, a subsequent call to task_done() tells the queue that the processing on the task is complete.
If a join() is currently blocking, it will resume when all items have been processed (meaning that a task_done() call was received for every item that had been put() into the queue).
Raises a ValueError if called more times than there were items placed in the queue.
g) 生产者消费者模型
在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。
为什么要使用生产者和消费者模式?
在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。
什么是生产者消费者模式?
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
下面来学习一个最基本的生产者消费者模型的例子:
import threading import queue def producer(): for i in range(10): q.put("骨头 %s" % i ) print("开始等待所有的骨头被取走...") q.join() print("所有的骨头被取完了...") def consumer(n): while q.qsize() >0: print("%s 取到" %n, q.get()) q.task_done() #告知这个任务执行完了 q = queue.Queue() p = threading.Thread(target=producer,) p.start() c1 = consumer("Jack")
示例1-生产者消费者模型
再来一个:
import time,random import queue,threading q = queue.Queue() def Producer(name): count = 0 while count <20: time.sleep(random.randrange(3)) q.put(count) print('Producer %s has produced %s baozi..' %(name, count)) count +=1 def Consumer(name): count = 0 while count <20: time.sleep(random.randrange(4)) if not q.empty(): data = q.get() print(data) print('\033[32;1mConsumer %s has eat %s baozi...\033[0m' %(name, data)) else: print("-----no baozi anymore----") count +=1 p1 = threading.Thread(target=Producer, args=('A',)) c1 = threading.Thread(target=Consumer, args=('B',)) p1.start() c1.start()
示例2
3. GIL全局解释器锁(面试必会)
Python中无论几核,同一时间只有一个线程在执行。
线程锁(互斥锁)
一个进程下可以启动多个线程,多个线程共享父进程的内存空间,也就意味着每个线程可以访问同一份数据,此时,如果2个线程同时要修改同一份数据,会出现什么状况?
import threading import time def run(n): global num time.sleep(2) num += 1 num = 0 t_objs = [] for i in range(1000): t = threading.Thread(target=run, args=("t_%s" % i,)) t.start() t_objs.append(t) for t in t_objs: t.join() print("===all threads has finished") print("num:", num)
线程锁
正常来讲,这个num结果应该是1000, 但在python 2.7上多运行几次,会发现,最后打印出来的num结果不总是1000,为什么每次运行的结果不一样呢? 哈,很简单,假设你有A,B两个线程,此时都 要对num 进行减1操作, 由于2个线程是并发同时运行的,所以2个线程很有可能同时拿走了num=0这个初始变量交给cpu去运算,当A线程去处完的结果是1,但此时B线程运算完的结果也是1,两个线程同时CPU运算的结果再赋值给num变量后,结果就都是1。那怎么办呢? 很简单,每个线程在要修改公共数据时,为了避免自己在还没改完的时候别人也来修改此数据,可以给这个数据加一把锁, 这样其它线程想修改此数据时就必须等待你修改完毕并把锁释放掉后才能再访问此数据。
为了让上边代码每次都输出num是1000的话,必须再加一把锁:
import threading, time def run1(): print("grab the first part data") lock.acquire() global num num += 1 lock.release() return num def run2(): print("grab the second part data") lock.acquire() global num2 num2 += 1 lock.release() return num2 def run3(): lock.acquire() res = run1() print('--------between run1 and run2-----') res2 = run2() lock.release() print(res, res2) if __name__ == '__main__': num, num2 = 0, 0 lock = threading.RLock() for i in range(10): t = threading.Thread(target=run3) t.start() while threading.active_count() != 1: print(threading.active_count()) else: print('----all threads done---') print(num, num2)
输出num为1000
加上锁后程序就变串行了,为了避免程序变慢,别在子线程里加sleep等类似操作!
GIL VS Lock :
既然Python已经有一个GIL来保证同一时间只能有一个线程来执行了,为什么这里还需要lock?注意,这里的lock是用户级的lock,跟那个GIL没关系 ,具体我们通过下图来看一下:
递归锁:
threading.RLock()不会出现锁死情况,说白了就是在一个大锁中还要再包含子锁。
import threading, time def run1(): print("grab the first part data") lock.acquire() global num num += 1 lock.release() return num def run2(): print("grab the second part data") lock.acquire() global num2 num2 += 1 lock.release() return num2 def run3(): lock.acquire() res = run1() print('--------between run1 and run2-----') res2 = run2() lock.release() print(res, res2) if __name__ == '__main__': num, num2 = 0, 0 lock = threading.RLock() for i in range(10): t = threading.Thread(target=run3) t.start() while threading.active_count() != 1: print(threading.active_count()) else: print('----all threads done---') print(num, num2)
threading.RLock()
Python自动化 【第九篇】:Python基础-线程、进程及python GIL全局解释器锁的更多相关文章
- Python之路-python(paramiko,进程和线程的区别,GIL全局解释器锁,线程)
一.paramiko 二.进程.与线程区别 三.python GIL全局解释器锁 四.线程 语法 join 线程锁之Lock\Rlock\信号量 将线程变为守护进程 Event事件 queue队列 生 ...
- 并发编程(五)——GIL全局解释器锁、死锁现象与递归锁、信号量、Event事件、线程queue
GIL.死锁现象与递归锁.信号量.Event事件.线程queue 一.GIL全局解释器锁 1.什么是全局解释器锁 GIL本质就是一把互斥锁,相当于执行权限,每个进程内都会存在一把GIL,同一进程内的多 ...
- 【python自动化第九篇:进程,线程,协程】
简要: paramiko模块 进程与线程 python GIL全局解释器锁 一.PARAMIKO模块 实现远程ssh执行命令 #!/usr/bin/env python # -*- coding:ut ...
- python 之 并发编程(守护线程与守护进程的区别、线程互斥锁、死锁现象与递归锁、信号量、GIL全局解释器锁)
9.94 守护线程与守护进程的区别 1.对主进程来说,运行完毕指的是主进程代码运行完毕2.对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕详细解释:1.主 ...
- GIL全局解释器锁、死锁现象、python多线程的用处、进程池与线程池理论
昨日内容回顾 僵尸进程与孤儿进程 # 僵尸进程: 所有的进程在运行结束之后并不会立刻销毁(父进程需要获取该进程的资源) # 孤儿进程: 子进程正常运行 但是产生该子进程的父进程意外死亡 # 守护进程: ...
- python GIL全局解释器锁,多线程多进程效率比较,进程池,协程,TCP服务端实现协程
GIL全局解释器锁 ''' python解释器: - Cpython C语言 - Jpython java ... 1.GIL: 全局解释器锁 - 翻译: 在同一个进程下开启的多线程,同一时刻只能有一 ...
- python并发编程-多线程实现服务端并发-GIL全局解释器锁-验证python多线程是否有用-死锁-递归锁-信号量-Event事件-线程结合队列-03
目录 结合多线程实现服务端并发(不用socketserver模块) 服务端代码 客户端代码 CIL全局解释器锁****** 可能被问到的两个判断 与普通互斥锁的区别 验证python的多线程是否有用需 ...
- python基础--GIL全局解释器锁、Event事件、信号量、死锁、递归锁
ps:python解释器有很多种,最常见的就是C python解释器 GIL全局解释器锁: GIL本质上是一把互斥锁:将并发变成串行,牺牲效率保证了数据的安全 用来阻止同一个进程下的多个线程的同时执行 ...
- 【转】进程、线程、 GIL全局解释器锁知识点整理
转自:https://www.cnblogs.com/alex3714/articles/5230609.html 本节内容 操作系统发展史介绍 进程.与线程区别 python GIL全局解释器锁 线 ...
随机推荐
- http_build_query()就是将一个数组转换成url 问号?后面的参数字符串,并且会自动进行urlencode处理,及它的逆向函数
http_build_query()就是将一个数组转换成url 问号?后面的参数字符串,并且会自动进行urlencode处理 例如: $data = array( 'foo'=>'bar', ' ...
- crontab不能正确执行的问题
近期在部署crontab任务的时候,总是遇到在shell中单独执行正常,但是放到crontab定时执行出错的问题.若出现这类场景,九成就是环境变量的问题. 因为我的定制任务,基本上都需要使用sqlpl ...
- C语言实现简单线程池(转-Newerth)
有时我们会需要大量线程来处理一些相互独立的任务,为了避免频繁的申请释放线程所带来的开销,我们可以使用线程池.下面是一个C语言实现的简单的线程池. 头文件: 1: #ifndef THREAD_POOL ...
- CentOS6.8 MySQL 5.6实现主从复制
主库操作 1.将mysqldump命令添加到/usr/bin中 ln -s /application/mysql/bin/mysqldump /usr/bin/ 2.开启master上的log-bin ...
- 用Sublime Text搭建简易IDE编写Verilog代码
前言 Verilog是一种硬件描述语言(HDL),该语言在Windows上有集成开发环境可以使用,如ModelSim,但ModelSim的编辑器不太好用因此笔者萌生了用Sublime Text3来编写 ...
- Nginx 负载均衡学习
nginx作为负载均衡服务器,用户请求先到达nginx,再由nginx根据负载配置将请求转发至 tomcat服务器. nginx负载均衡服务器 tomcat1服务器 tomcat2服务器 1.1 ...
- JQuery AJAX 解析获得的JSON数据
下面的解析的Json是一个二级循环. <!DOCTYPE html> <html> <head> <script src="https://code ...
- thrift demo
基于上一篇博客,安装thrift complier之后,就需要进行跑跑程序,来看看是否如同预期的那种效果. 前面的thrift compiler的主要作用,其实就是为了IDL的,就是防止客户端和服务端 ...
- 7、java实现的两种单例模式
/* 两种单例模式的演示 */ //饿汉式 class Signal { private Signal(){} private Signal s = new Signal(); public stat ...
- mac idea 设置
鼠标悬停显示文档注释:preferences->Editor->General:勾选 show quick documentation on mouse move 智能提示模糊搜索:pre ...