同步锁/递归锁/协程

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. Javascript实现继承

    以下转自阮一峰的文章:http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_inheritance_continued.h ...

  2. HashSet源码分析

    在java集合中有一种集合Set(集),他有两个实现类,分别是HashSet,TreeSet.下面仔细分析HashSet源码. 看了HashSet的源码就会发现HashSet的底层实现是利用HashM ...

  3. 什么是DOM,DOM level 1\2\3 的区别是什么

    DOM 文档对象模型(Document Object Model,简称DOM),是W3C组织推荐的处理可扩展标志语言的标准编程接口.Document Object Model的历史可以追溯至1990年 ...

  4. poj-3660-cows contest(不懂待定)

    Description N (1 ≤ N ≤ 100) cows, conveniently numbered 1..N, are participating in a programming con ...

  5. spring - boot 监控管理模块搭建

    Spring-Actuator是Spring-boot对应用监控的集成模块,提供了我们对服务器进行监控的支持,使我们更直观的获取应用程序中加载的应用配置.环境变量.自动化配置报告等. 使用Spring ...

  6. Oracle修改字段类型方法小技巧

    有一个表名为tb,字段段名为name,数据类型nchar(20). 1.假设字段数据为空,则不管改为什么字段类型,可以直接执行:alter table tb modify (name nvarchar ...

  7. Java基础学习笔记二十八 管家婆综合项目

    本项目为JAVA基础综合项目,主要包括: 熟练View层.Service层.Dao层之间的方法相互调用操作.熟练dbutils操作数据库表完成增删改查. 项目功能分析 查询账务 多条件组合查询账务 添 ...

  8. drbd(三):drbd的状态说明

    本文目录:1.drbd配置文件2.状态 2.1 连接状态(connect state,cs)和复制状态 2.2 角色状态(roles,ro) 2.3 磁盘状态(disk state,ds) 2.4 I ...

  9. windows下apache报os 10048错误

    在apache的bin目录下运行httpd -k install,报错os10048 (错误信息是跟443端口有关),网上的答案说的是改掉httpd.conf里的默认端口或者关闭占用端口的进程,默认端 ...

  10. 【alpha冲刺】随笔合集

    Daily Scrum Meeting 第一天 [Alpha]Daily Scrum Meeting第一次 第二天 [Alpha]Daily Scrum Meeting第二次 第三天 [Alpha]D ...