python的进程与线程(三)
线程的锁
1.几个概念
讲起线程的锁,先要了解几个概念:什么是并行?什么是并发?什么是同步?什么是异步?
并发:是指系统具有处理多个任务(动作)的能力
并行:是指系统具有 同时 处理多个任务(动作)的能力,所以并行是并发的子集
同步:当进程执行到一个IO(比如等待外部数据)的时候,需要等待就是同步
异步:当进程执行到一个IO(比如等待外部数据)的时候,不需要等待,直到接收到数据成功后再返回来执行,就是异步
2.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在执行的时候会淡定的在同一时刻只允许一个线程运行
大部分人使用的python解释器都是c写的,也叫Cpython,在解释器中python的开发者“Guido叔”就在解释器里面加了一道锁,就是GIL,什么意思呢?我们从例子开始讲解:
def add():
sum=0 for i in range(10000000):
sum+=i
print("sum",sum) def mul():
sum2=1
for i in range(1,100000):
sum2*=i
print("sum2",sum2) import threading,time start=time.time() t1=threading.Thread(target=add)
t2=threading.Thread(target=mul) l=[]
l.append(t1)
l.append(t2) for t in l:
t.start() for t in l:
t.join() # add()
# mul() print("cost time %s"%(time.time()-start))
运行结果是:
sum 49999995000000
sum2 太长了
cost time 8.665002822875977
换成串行的方式来运行一下:
sum 49999995000000
sum2 太长了
cost time 8.955007314682007
从结果我们发现,多线程和直接串行运行耗时差不多,而且如果你用的是2.7的解释器(我的是3.7),甚至串行还会比多线程快,但是从第二篇的例子可以知道多线程确实能缩短运行时间提高效率的,咋这里就不行,这是为什么呢?就是因为python的GIL!再看看上面的那句话,无论你启多少个线程,你有多少个cpu, Python在执行的时候会淡定的在同一时刻只允许一个线程运行。至于GIL是好还是不好,各有各的看法,引入的目的就是为了实现不同线程对共享资源访问的互斥,才引入了GIL。
这时我们可以知道,一个CPU在执行多线程的时候,是并发且异步的,上面已经讲过概念了,就像下面这个图一样,CPU不停的在我们写的三个线程之间来回切换,切换的情况有两种,一种是遇到类的IO,一种就是自然的时间轮询(CPU会自动切换,周期是纳秒级别的,所及感觉不到)。按道理像t1和t2这样互相切换会比直接一个个的给CPU处理要慢,但是经过python解释器的其他处理,才会出现现在这个结果。
那么为什么第二篇的例子里面,确实线程节约了时间呢?我们可以把任务分成两种,一种是:IO密集型,一种是:计算密集型,我们可以把sleep直接看成是IO,这样就可以解释清楚了。遇到sleep的时候,就切换到其他线程处理,然后等sleep结束,再返回继续处理这个线程。所以得出一个结论:
对于IO密集型的任务:python的多线程是有意义的
对于计算密集型的任务:就不推荐python多线程了
那么对于计算密集型的任务没有办法解决呢?肯定是有的,一个是你可以用其他语言代替(感觉会被打),还有一个是多进程,虽然消耗比线程大,但开十几个的话,不是很明显,后面会讲到。当然还有其他方法,这里就不多讲了。
3.同步锁
我们先看一个例子,运行之后看看会有什么问题:
import threading
import time
def sub():
global num temp=num
time.sleep(0.001)
num=temp-1 num=100 l=[] for i in range(100):
t=threading.Thread(target=sub)
t.start()
l.append(t) for t in l:
t.join() print (num)
我运行了3次(结果分别是:82、87、84),每次结果都不一样,但是我目的是想让100减一百次1,最终答案应该是0,联想到上面讲的CPU在不停的切换,这里应该出了问题,可以这样推想:如果要减100次,让一百个朋友依次传递下去,最后结果肯定是0,这也就是串行的方式来做。现在用多线程的方式,情况就是一百个朋友竞争着从我这里拿数据num,第一个人拿到了,但是遇到了sleep,就切换了另一个人,此时拿到的还是100,再次遇到sleep,换一个人拿到的还是100,所以如果sleep的时间足够大(比如1s),那么最后得到的结果应该是99,那么问题来了,这该如何是好?
多个线程都在同时操作同一个共享资源,所以造成了资源破坏,怎么办呢?(join会造成串行,失去所线程的意义)
这个时候,我们希望的是将sleep和上下两步捆绑在一起,这样就不会IO切换了,通过同步锁可以解决这种问题,同步锁,顾名思义就是一把锁嘛,把一些东西锁起来
lock=threading.Lock()这就造好一把锁了,然后开始吧:
import threading
import time
def sub():
global num
# num-=1
# print ("ok")
lock.acquire()
temp=num
time.sleep(0.001)
num=temp-1
lock.release() num=100 l=[]
lock=threading.Lock() for i in range(100):
t=threading.Thread(target=sub)
t.start()
l.append(t) for t in l:
t.join() print (num)
现在怎么运行,结果都是0,大功告成!
4.线程死锁和递归锁
在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁,因为系统判断这部分资源都正在使用,所有这两个线程在无外力作用下将一直等待下去。下面是一个死锁的例子:
import threading,time class myThread(threading.Thread):
def doA(self):
lockA.acquire()
print(self.name,"gotlockA",time.ctime())
time.sleep(3)
lockB.acquire()
print(self.name,"gotlockB",time.ctime())
lockB.release()
lockA.release() def doB(self):
lockB.acquire()
print(self.name,"gotlockB",time.ctime())
time.sleep(2)
lockA.acquire()
print(self.name,"gotlockA",time.ctime())
lockA.release()
lockB.release() def run(self):
self.doA()
self.doB()
if __name__=="__main__": lockA=threading.Lock()
lockB=threading.Lock()
threads=[]
for i in range(5):
threads.append(myThread())
for t in threads:
t.start()
for t in threads:
t.join()
print('ending...')
运行结果:
Thread-1 gotlockA Fri Apr 12 10:42:16 2019
Thread-1 gotlockB Fri Apr 12 10:42:19 2019
Thread-1Thread-2 gotlockBgotlockA Fri Apr 12 10:42:19 2019Fri Apr 12 10:42:19 2019
补充知识点1:self.name可以拿到这个线程的默认名字,就是Thread-1和Thread-2。现在我们发现程序不会停止了,一直卡在这里,这个时候,就像两个孩子在争执,我手里拿了你的东西,你手里拿了我的东西,谁也不想先拿出来交换,这就死锁了,这种情况怎么办呢?用递归锁:
现在我全部换成一把锁,叫递归锁:
import threading
import time class MyThread(threading.Thread): def actionA(self): r_lcok.acquire() #count=1
print(self.name,"gotA",time.ctime())
time.sleep(2)
r_lcok.acquire() #count=2 print(self.name, "gotB", time.ctime())
time.sleep(1) r_lcok.release() #count=1
r_lcok.release() #count=0 def actionB(self): r_lcok.acquire()
print(self.name, "gotB", time.ctime())
time.sleep(2) r_lcok.acquire()
print(self.name, "gotA", time.ctime())
time.sleep(1) r_lcok.release()
r_lcok.release() def run(self): self.actionA()
self.actionB() if __name__ == '__main__': # A=threading.Lock()
# B=threading.Lock() r_lcok=threading.RLock()
L=[] for i in range(5):
t=MyThread()
t.start()
L.append(t) for i in L:
i.join() print("ending....")
现在就可以运行完整了:
Thread-1 gotA Fri Apr 12 10:47:46 2019
Thread-1 gotB Fri Apr 12 10:47:48 2019
Thread-2 gotA Fri Apr 12 10:47:49 2019
Thread-2 gotB Fri Apr 12 10:47:51 2019
Thread-2 gotB Fri Apr 12 10:47:52 2019
Thread-2 gotA Fri Apr 12 10:47:54 2019
Thread-4 gotA Fri Apr 12 10:47:55 2019
Thread-4 gotB Fri Apr 12 10:47:57 2019
Thread-5 gotA Fri Apr 12 10:47:58 2019
Thread-5 gotB Fri Apr 12 10:48:00 2019
Thread-1 gotB Fri Apr 12 10:48:01 2019
Thread-1 gotA Fri Apr 12 10:48:03 2019
Thread-3 gotA Fri Apr 12 10:48:04 2019
Thread-3 gotB Fri Apr 12 10:48:06 2019
Thread-3 gotB Fri Apr 12 10:48:07 2019
Thread-3 gotA Fri Apr 12 10:48:09 2019
Thread-5 gotB Fri Apr 12 10:48:10 2019
Thread-5 gotA Fri Apr 12 10:48:12 2019
Thread-4 gotB Fri Apr 12 10:48:13 2019
Thread-4 gotA Fri Apr 12 10:48:15 2019
ending....
这个递归锁的原理也很简单,就是通过一个计数器,看当前有几个人在用这把锁,为了支持在同一线程中多次请求同一资源,python提供了“可重入锁”:threading.RLock。RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次acquire。直到一个线程所有的acquire都被release,其他的线程才能获得资源。不过这种情况比较少见,了解一下就好。
在递归函数中的应用:
import threading
n = 2
max_n = 10
x = 0
lock = threading.RLock()
def countup(m):
global x
if m > 0:
lock.acquire()
x += 1
countup(m - 1)
lock.release()
print ('%s: %s\r\n' % (threading.currentThread().getName(), x))
else:
return
for i in range(n):
t = threading.Thread(target=countup, args=(max_n,))
t.start()
在线程中比较常用的就是这两把锁,当然还有其他锁,比如Condition等,这里就过多讲解了,下面要讲的是信号量、同步对象、队列等知识点。
python的进程与线程(三)的更多相关文章
- Python的进程与线程--思维导图
Python的进程与线程--思维导图
- Python创建进程、线程的两种方式
代码创建进程和线程的两种方式 """ 定心丸:Python创建进程和线程的方式基本都是一致的,包括其中的调用方法等,学会一个 另一个自然也就会了. "" ...
- python之进程与线程
什么是操作系统 可能很多人都会说,我们平时装的windows7 windows10都是操作系统,没错,他们都是操作系统.还有没有其他的? 想想我们使用的手机,Google公司的Androi ...
- python的进程与线程(二)
线程 之前了解了操作系统的发展史,也知道了进程和线程的概念,归纳一下就是: 进程:本质上就是一段程序的运行过程(抽象的概念) 线程:最小的执行单元,是进程的实体 ...
- python之进程和线程
1 操作系统 为什么要有操作系统 ? 操作系统位于底层硬件与应用软件之间的一层 工作方式:向下管理硬件,向上提供接口 操作系统进程切换: 出现IO操作 固定时间 2 进程和线程的概念 进程就是一个程序 ...
- Python中进程和线程的总体区别
Num01–>线程 线程是操作系统中能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位. 一个线程指的是进程中一个单一顺序的控制流. 一个进程中可以并发多条线程,每条线程并行 ...
- Python的进程、线程和threading模块
(注:本文部分内容摘自互联网,由于作者水平有限,不足之处,还望留言指正.) 怀念在学校念书的时候,我不小心触碰到了错误,老师会说:你错了:而我却总是倔强得以为自己没错.我的内心是不屑的,直到在真理面前 ...
- Python基础-进程和线程
一.进程和线程的概念 首先,引出“多任务”的概念:多任务处理是指用户可以在同一时间内运行多个应用程序,每个应用程序被称作一个任务.Linux.windows就是支持多任务的操作系统,比起单任务系统它的 ...
- python的进程与线程
一.进程与线程的相关概念 1.什么是进程 进程是一个程序在一个数据集上的一次动态执行过程. 进程一般由程序,数据集,进程控制块三部分组成. 2.什么是线程 线程也叫轻量级进程,它是一个基本的CPU执行 ...
- python 16 进程和线程
进程和线程 很多同学都听说过,现代操作系统比如Mac OS X,UNIX,Linux,Windows等,都是支持“多任务”的操作系统. 什么叫“多任务”呢?简单地说,就是操作系统可以同时运行多个任务. ...
随机推荐
- Java系列2 --- 你真的知道Java的String对象么?
在上一篇中说道这篇文章会说java的动态绑定机制,由于这个知识点放在继承中讲会比较合适,说以在这篇文章中先来详细的说说String对象吧. 只要学过Java的同学,我们都知道Java一共有8中基本 ...
- FTP用户无法登陆排错详解
FTP作为一种简单便捷的文件共享技术,在许多企业内部得到使用.若启用FTP的验证控制,管理员更可对不同的用户设置不同的访问权限,控制用户对特定内容的访问.IIS中的FTP站点只有一种验证方式,即基本验 ...
- forwardport--源码笔记--注释
failed:", err.Error()) } }() } // log.Println("forwardPort ...
- hibernate MTM 联合主键
//适用于表里没有其他列,只有主键 //Course.java实体类 package com.tao.pojo; import java.util.HashSet; import java.util. ...
- bzoj4476 [Jsoi2015]送礼物
化简式子 $M>=m+ans*(r-l+k)$ 发现$M,m$确定时,总区间长度越小越好,于是假定右端点为最小值$M+ans*l>=m+ans*r+ans*k$, 右面都确定了,但最大值仍 ...
- BZOJ_4006_[JLOI2015]管道连接_斯坦纳树
BZOJ_4006_[JLOI2015]管道连接_斯坦纳树 题意: 小铭铭最近进入了某情报部门,该部门正在被如何建立安全的通道连接困扰. 该部门有 n 个情报站,用 1 到 n 的整数编号.给出 m ...
- Scrapy爬虫框架(实战篇)【Scrapy框架对接Splash抓取javaScript动态渲染页面】
(1).前言 动态页面:HTML文档中的部分是由客户端运行JS脚本生成的,即服务器生成部分HTML文档内容,其余的再由客户端生成 静态页面:整个HTML文档是在服务器端生成的,即服务器生成好了,再发送 ...
- COGS2421 [HZOI 2016]简单的Treap
题面见这里 大概是个模板题 Treap暴力插入的做法太暴力了并不优美 这里就需要用到笛卡尔树的构造方法,定义见这里 在 假的O(n) 的时间内构造一棵Treap 把元素从小到大排序 这样从小到大插入时 ...
- 解决tomcat部署项目中碰到的几个问题
在tomcat上部署项目并进行测试,经常会碰到各种问题.在不同的操作系统上部署,对问题的解决也会有一些差异. 1 发现问题 1.1 项目部署 先将项目达成war包,放到tomcat的webapps目录 ...
- 『随笔』.Net 底层 数组[] 的 基本设计探秘 512 子数组
static void Main(string[] args) { Console.ReadKey(); //初始化数组 不会立即开辟内存字节, 只有实际给数组赋值时 才会开辟内存 // //猜测数组 ...