同步锁/递归锁/协程

1 同步锁

锁通常被用来实现对共享资源的同步访问,为每一个共享资源创建一个Lock对象,当你需需要访问该资源时,调用acquire()方法来获取锁对象(如果其他线程已经获得了该锁,则当前线程需要等待其被释放),待资源访问完后,在调用release方式释放锁:

import threading
import time def subnum():
global num
# num-=1
lock.acquire() #对用户进行加锁处理
#加锁只对用户数据 等第一个释放完之后才能执行下一个
temp=num
time.sleep(0.01)
num=temp-1 #对此公共变量进行-1操作
lock.release() #然后进程释放锁 num=100
l=[]
lock=threading.Lock()
for i in range(100):
t=threading.Thread(target=subnum)
t.start()
l.append(t) for t in l:
t.join() print("result:",num) 执行结果:
result: 0

2 死锁 

所谓死锁:是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程成为死锁进程。

import threading
import time
mutexA=threading.Lock()
mutexB=threading.Lock() class MyThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
self.fun1()
self.fun2() def fun1(self):
mutexA.acquire()
print("I am %s,get res:%s---%s"%(self.name,"ResA",time.time())) mutexB.acquire()
print("I am %s,get res:%s---%s"%(self.name,"ResB",time.time()))
mutexB.release() mutexA.release() def fun2(self):
mutexB.acquire()
print("I am %s,get res:%s---%s"%(self.name,"ResB",time.time()))
time.sleep(0.2) mutexA.acquire()
print("I am %s,get res:%s---%s"%(self.name,"ResB",time.time()))
mutexA.release() mutexA.release() if __name__ == '__main__':
print("start------------%s",time.time()) for i in range(0,10):
my_thread=MyThread()
my_thread.start() 执行结果:
start------------%s 1494311688.1136441
I am Thread-1,get res:ResA---1494311688.1136441
I am Thread-1,get res:ResB---1494311688.1136441
I am Thread-1,get res:ResB---1494311688.1136441
I am Thread-2,get res:ResA---1494311688.1136441

  

3 递归锁

在python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。

import threading
import time Rlock=threading.RLock()
class MyThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self) def run(self):
self.fun1()
self.fun2() def fun1(self):
Rlock.acquire() #如果锁被占用,则阻塞在这里,等待锁的释放
print("I am %s,get res:%s---%s"%(self.name,"ResA",time.time())) Rlock.acquire() #count=2
print("I am %s,get res:%s---%s"%(self.name,"ResB",time.time())) Rlock.release() #count-1
Rlock.release() #count-1=0 def fun2(self):
Rlock.acquire() #count=1
print("I am %s,get res:%s---%s"%(self.name,"ResB",time.time()))
time.sleep(0.2) Rlock.acquire() #count=2
print("I am %s,get res:%s---%s"%(self.name,"ResA",time.time()))
Rlock.release() #coun-1
Rlock.release() #count-1 if __name__ == '__main__':
print("start-----------%s"%time.time()) for i in range(0,10):
my_thread=MyThread()
my_thread.start()

  

4 Event对象

线程的一个关键特性是每个线程都是独立运行且状态不可预测。我们需要使用threading库中的Event对象,对象包含一个可由线程设置的信号标志,它允许线程等待某些事情的发生,在初始情况下,Event对象中的信号标志被设置为假。如果有线程等待一个Event对象,而这个Event对象的标志位假,那么这个线程将会被一直阻塞直至该标志为真。一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事情,继续执行。

event.isSet():返回event的状态值;

event.wait():如果 event.isSet()==False将阻塞线程;

event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;

event.clear():恢复event的状态值为False。

例如,我们有多个线程从Redis队列中读取数据来处理,这些线程都要尝试去链接Redis的服务,一般情况下,如果Redis连接不成功,这各个线程的代码中,都会去尝试重新连接,那么我们就可以采用threading.Event机制来协调各个工作线程的连接操作:主线程中会去尝试连接Redis服务,如果正常的话,触发事件,各工作线程会尝试连接Redis服务。

#event 就是线程线程之间通信使用的
#event wait  默认值为False  就会被阻塞
#set   改成Ture  就是向下执行

import threading
import time
import logging logging.basicConfig(level=logging.DEBUG,format="(%(threadName)-10s)%(message)s",) def worker(event):
logging.debug("waiting for redis ready...")
event.wait() #if flag=False 阻塞,等待flag=true继续执行
logging.debug('redis ready,and connect to redis server and do some work[%s]',time.time())
time.sleep(1) def main():
readis_ready=threading.Event() #flga=False
t1=threading.Thread(target=worker,args=(readis_ready,),name="t1")
t1.start() t2=threading.Thread(target=worker,args=(readis_ready,),name="t2")
t2.start() logging.debug("first of all,check redis server,make sure it is ok,and then trigger the redis teady event")
time.sleep(3) #simulate the check propress
readis_ready.set()
if __name__ == '__main__':
main() 执行结果:
(t1 )waiting for redis ready...
(t2 )waiting for redis ready...
(MainThread)first of all,check redis server,make sure it is ok,and then trigger the redis teady event
(t1 )redis ready,and connect to redis server and do some work[1494314141.0479438]
(t2 )redis ready,and connect to redis server and do some work[1494314141.0479438]

threading.Event的wait方法还接受了一个超时参数,默认情况下如果事件一致没有发生,wait方法会一直阻塞下去,而加入这个超时参数之后,如果阻塞时间超过这个参数设定的值之后,wait方法会返回。对应于上面的应用场景,如果Redis服务器一致没有启动,我们希望子线程能够打印一些日志来不断地提醒我们当前没有一个可以连接的Redis服务,我们就可以通过设置这个超时参数来达成这样的目的。

import threading
import time
import logging logging.basicConfig(level=logging.DEBUG,format="(%(threadName)-10s)%(message)s",) def worker(event):
logging.debug("waiting for redis ready....")
while not event.is_set():#现在是false的时候
event.wait(3) #每隔三秒钟提示一下 #一直打印
logging.debug("wait........")
logging.debug("redis ready,and connect to redis server and do mome work [%s]",time.ctime())
time.sleep(1) def main():
readis_ready=threading.Event() #flga=False
t1=threading.Thread(target=worker,args=(readis_ready,),name="t1")
t1.start() t2=threading.Thread(target=worker,args=(readis_ready,),name="t2")
t2.start() logging.debug("first of all,check redis server,make sure it is ok,and then trigger the redis teady event")
time.sleep(3) #simulate the check propress
readis_ready.set()
if __name__ == '__main__':
main() 执行结果
(t1 )waiting for redis ready....
(t2 )waiting for redis ready....
(MainThread)first of all,check redis server,make sure it is ok,and then trigger the redis teady event
(t1 )wait........
(t1 )redis ready,and connect to redis server and do mome work [Tue May 9 16:56:18 2017]
(t2 )wait........
(t2 )redis ready,and connect to redis server and do mome work [Tue May 9 16:56:18 2017]

  

5 信号量  

Semaphore管理一个内置的计数器:

每当调用acquire()时内置计数器-1

调用release()时内置计数器+1

基数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()。

实例:(同时只有5个线程可以获得semaphore,即可以限制最大连接数为5)

import threading
import time
semaphore=threading.Semaphore(5)
def func():
semaphore.acquire() #相当于加了一把锁 同时跑五个线程
print(threading.currentThread().getName()+"get semaphore")
time.sleep(3) #等待3秒后
semaphore.release() #五个线程同时释放掉 for i in range(20):
t1=threading.Thread(target=func)
t1.start()

6 multiprocessing模块

由于GIL的存在,python中的多线程其实并不是真正的多线程,如果想要充分地使用多核CPU的资源,在python中大部分情况需要使用多进程。

Multiprocessing包是python中的多进程管理包。与threading.Thread类似,它可以利用multiprocessing.Process对象来创建一个进程。该进程可以运行在python程序内部编写的函数。该Process对象与Thread对象的用法相同,也有start(),run(),join()的方法。此外multiprocessing包中也有Lock/Event/Semaphore/Condition类(这些对象可以像多线程那样,

普通通过参数传递给各个进程),用以同步进程,其用法与threading包中的同名类一致。所以,multiprocessing的很大一部分与threading使用同一套API,只不过换到了多进程的情境。

Python的进程调用:

Process类调用

from multiprocessing import Process

import time

def f(name):
print("hello",name,time.ctime())
time.sleep(5) if __name__ == '__main__':
p_l=[]
for i in range(3):
p=Process(target=f,args=("alvin:%s"%i,))
p_l.append(p)
p.start() for p in p_l:
p.join()
print("end") 执行结果:
hello alvin:0 Tue May 9 17:49:10 2017
hello alvin:1 Tue May 9 17:49:10 2017
hello alvin:2 Tue May 9 17:49:10 2017
end

继承Process类调用

from multiprocessing import Process
import time
class MyProcess(Process):
def __init__(self):
super().__init__() def run(self):
print("hello",self.name,time.ctime())
time.sleep(1) if __name__ == '__main__':
p_l=[]
for i in range(3):
p=MyProcess()
p.start()
p_l.append(p) for p in p_l:
p.join() print("end")

Process类

构造方法:

Process([group [, target [, name [, args [, kwargs]]]]])

  group: 线程组,目前还没有实现,库引用中提示必须是None; 
  target: 要执行的方法; 
  name: 进程名; 
  args/kwargs: 要传入方法的参数。

实例方法:

  is_alive():返回进程是否在运行。

  join([timeout]):阻塞当前上下文环境的进程程,直到调用此方法的进程终止或到达指定的timeout(可选参数)。

  start():进程准备就绪,等待CPU调度

  run():strat()调用run方法,如果实例进程时未制定传入target,这star执行t默认run()方法。

  terminate():不管任务是否完成,立即停止工作进程

属性:

  daemon:和线程的setDeamon功能一样

  name:进程名字。

  pid:进程号。

from multiprocessing import Process
import os
import time
def info(name): print("name:",name)
print("parent process:",os.getppid())
print("process id:",os.getpid())
print("-----------")
time.sleep(2) def foo(name):
info(name) if __name__ == '__main__':
info("main process line") p1=Process(target=info,args=("alvin",))
p2=Process(target=info,args=("egon",))
p1.start()
p2.start() p1.join()
p2.join()
print("ending") 执行结果:
name: main process line
parent process: 8032
process id: 9400
-----------
name: alvin
parent process: 9400
process id: 9252
-----------
name: egon
parent process: 9400
process id: 10556
-----------
ending

通过tasklist检测每一个进程号(PID)对应的进程名

  

7 协程

优点:1 由于单线程,不能再切换

2 不再有任何锁的概念

yield与携程:产生了并发

yeild与携程

import  time

def consumer():#消费者的
r=""
while True:
#3 consumer通过yield拿到消息,处理,又通过yield把结果传回
n=yield r
if not n:
return
print("[CONSUMER]--Consuming %s...."%n)
time.sleep(1)
r="200 OK" def produce(c):#消费者
next(c) #取生成对象的一个值
n=0
if __name__ == '__main__':
while n<5:
n=n+1
print("[PRODUCER]--Producing %s..."%n)
#2 然后,一旦产生了东西,通过c.send(n)切换到consumer执行
cr=c.send(n)
#4 produce拿到consumer处理的结果,继续生产下一条消息
print("[PRODUCER]Consumer return:%s"%cr)
#5 produce决定不生产了,通过c.close()关闭consumer,整个过程结束
c.close()
if __name__ == '__main__':
c=consumer() #产生一个生成器
produce(c) 执行结果:
[PRODUCER]--Producing 1...
[CONSUMER]--Consuming 1....
[PRODUCER]Consumer return:200 OK
[PRODUCER]--Producing 2...
[CONSUMER]--Consuming 2....
[PRODUCER]Consumer return:200 OK
[PRODUCER]--Producing 3...
[CONSUMER]--Consuming 3....
[PRODUCER]Consumer return:200 OK
[PRODUCER]--Producing 4...
[CONSUMER]--Consuming 4....
[PRODUCER]Consumer return:200 OK
[PRODUCER]--Producing 5...
[CONSUMER]--Consuming 5....
[PRODUCER]Consumer return:200 OK

  

Greenlet模块 是协程的基础

Greenlet机制的主要思想是:生成器函数或者协程函数中的yield语句挂起函数的执行,greenlet是python中实现我们所谓的”Coroutine(携程)”的一个基础库

from greenlet import greenlet

def test1():
print (12)
gr2.switch()
print (34)
gr2.switch() def test2():
print (56)
gr1.switch()
print (78) gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()

  

基于greenlet的框架

Gevent模块实现携程

Python通过yield提供了对协程的基本支持,但是不完全,而第三方的gevent为python提供了比较完成的协程支持

当一个greenlet遇到IO操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行,由于IO操作非常耗时,经常使程偶greenlet序处于等待状态,有了gevent为我们自动切换协程,就保证greenlet在运行,而不是等待IO。

import gevent
import time def foo():
print("running in foo")
gevent.sleep(2)
print("switch to foo again") def bar():
print("switch to bar")
gevent.sleep(5)
print("switch to bar again") start=time.time() gevent.joinall(
[gevent.spawn(foo),
gevent.spawn(bar)]
) print(time.time()-start)

当然,实际代码里,我们不会用gevent.sleep()去切换协程,而是在执行到IO操作时,gevent自动切换,代码如下:

from gevent import monkey
monkey.patch_all()
import gevent
from urllib import request
import time def f(url):
print('GET: %s' % url)
resp = request.urlopen(url)
data = resp.read()
print('%d bytes received from %s.' % (len(data), url)) start=time.time() gevent.joinall([
gevent.spawn(f, 'https://itk.org/'),
gevent.spawn(f, 'https://www.github.com/'),
gevent.spawn(f, 'https://zhihu.com/'),
]) #执行结果:
# f('https://itk.org/')
# f('https://www.github.com/')
# f('https://zhihu.com/') print(time.time()-start)

  

python--同步锁/递归锁/协程的更多相关文章

  1. Python进阶----线程基础,开启线程的方式(类和函数),线程VS进程,线程的方法,守护线程,详解互斥锁,递归锁,信号量

    Python进阶----线程基础,开启线程的方式(类和函数),线程VS进程,线程的方法,守护线程,详解互斥锁,递归锁,信号量 一丶线程的理论知识 什么是线程:    1.线程是一堆指令,是操作系统调度 ...

  2. python并发编程之线程/协程

    python并发编程之线程/协程 part 4: 异步阻塞例子与生产者消费者模型 同步阻塞 调用函数必须等待结果\cpu没工作input sleep recv accept connect get 同 ...

  3. Python 线程和进程和协程总结

    Python 线程和进程和协程总结 线程和进程和协程 进程 进程是程序执行时的一个实例,是担当分配系统资源(CPU时间.内存等)的基本单位: 进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其 ...

  4. Python PEP 492 中文翻译——协程与async/await语法

    原文标题:PEP 0492 -- Coroutines with async and await syntax 原文链接:https://www.python.org/dev/peps/pep-049 ...

  5. Python 多线程、进程、协程上手体验

    浅谈 Python 多线程.进程.协程上手体验 前言:浅谈 Python 很多人都认为 Python 的多线程是垃圾(GIL 说这锅甩不掉啊~):本章节主要给你体验下 Python 的两个库 Thre ...

  6. python并发编程之gevent协程(四)

    协程的含义就不再提,在py2和py3的早期版本中,python协程的主流实现方法是使用gevent模块.由于协程对于操作系统是无感知的,所以其切换需要程序员自己去完成. 系列文章 python并发编程 ...

  7. java面试-公平锁/非公平锁/可重入锁/递归锁/自旋锁谈谈你的理解

    一.公平锁/非公平锁/可重入锁/递归锁/自旋锁谈谈你的理解 公平锁:多个线程按照申请的顺序来获取锁. 非公平锁:多个线程获取锁的先后顺序与申请锁的顺序无关.[ReentrantLock 默认非公平.s ...

  8. python单线程,多线程和协程速度对比

    在某些应用场景下,想要提高python的并发能力,可以使用多线程,或者协程.比如网络爬虫,数据库操作等一些IO密集型的操作.下面对比python单线程,多线程和协程在网络爬虫场景下的速度. 一,单线程 ...

  9. python并发编程之asyncio协程(三)

    协程实现了在单线程下的并发,每个协程共享线程的几乎所有的资源,除了协程自己私有的上下文栈:协程的切换属于程序级别的切换,对于操作系统来说是无感知的,因此切换速度更快.开销更小.效率更高,在有多IO操作 ...

随机推荐

  1. 前端Blob对二进制流数据的处理方式

    var xhr = new XMLHttpRequest(); xhr.open("post", "/login/getCaptcher?t=" + Math. ...

  2. Tomact高并发&Servlet线程处理

    Servlet/JSP技术和ASP.PHP等相比,由于其多线程运行而具有很高的执行效率.由于Servlet/JSP默认是以多线程模式执行的,所以,在编写代码时需要非常细致地考虑多线程的安全性问题.然而 ...

  3. 深入解读 Js 中的面向对象编程

    前言:今天看了一篇文章觉得很不错,所以给大家分享一下,也许很多人都看过面向对象编程甚至写过这样博客,觉得面向对象编程就那样,没啥好说的,那可能是因为你对这方面知识已经了解,可以选择性跳过.那如果有更通 ...

  4. HA集群heartbeat配置--Nginx

    HA即(high available)高可用,又被叫做双机热备,用于关键性业务.简单理解就是,两台机器A和B,正常是A提供服务,B待命限制,当A宕机或服务宕掉,会切换至B机器继续提供服务.常用实现高可 ...

  5. Jmeter 后置处理器 BeanShell_PostProcessor 适用思考

    首先摘抄一段官方文档的话: The following BeanShell variables are set up for use by the script: log - (Logger) - c ...

  6. vue-axios基本用法

    废话不多说,直接搞事搞事. 首先安装axios: 1):npm install 2):npm install vue-axios --save 3):npm install qs.js --save ...

  7. id 选择器

    id 选择器 1.id 选择器可以为标有特定 id 的 HTML 元素指定特定的样式. (即也可以说,可以将已经预先定义的特定样式,通过id选择器,赋值指向HTML 元素) 2.HTML元素以id属性 ...

  8. Mac下安装virtualenv, 并在PyCharm中使用

    今天在安装一个leader写的package的时候,同事建议安装到虚拟环境中,再在PyCharm里使用该虚拟环境即可.此处记录下经过: 开发Python应用的时候,有时会遇到不同的Python应用依赖 ...

  9. Git忽略规则.gitignore梳理

    对于经常使用Git的朋友来说,.gitignore配置一定不会陌生.废话不说多了,接下来就来说说这个.gitignore的使用. 首先要强调一点,这个文件的完整文件名就是".gitignor ...

  10. c语言程序设计第4周编程练习(素数和)

    1 素数和(5分) 题目内容: 我们认为2是第一个素数,3是第二个素数,5是第三个素数,依次类推. 现在,给定两个整数n和m,0<n<=m<=200,你的程序要计算第n个素数到第m个 ...