python 异步io并发_Python高级编程和异步IO并发编程(二)

Python高级编程和异步IO并发编程

一、多线程、多进程和线程池编程

1、GIL

gil global interpreter lock (全局解释器锁)

python中一个线程对应于c语言中的一个线程 (cpython)

gil使得同一个时刻只有一个线程在一个cpu上执行字节码, 无法将多个线程映射到多个cpu上执行

GIL主动释放的情况:

gil会根据执行的字节码行数以及时间片释放gil

gil在遇到io的操作时候会主动释放

2、多线程编程 --threading

操作系统能调度的最小单元是线程

对于io操作来说,多线程和多进程性能差别不大

setdaemon 方法:守护线程,主线程运行完毕,立刻结束所有线程

join 方法:线程阻塞,无论主线程是否运行完毕,都需等待子线程执行完成才能结束

创建多线程两种方式

1)直接使用:

importtimeimportthreadingdefget_detail_html(url):print("get detail html started")

time.sleep(2)print("get detail html end")defget_detail_url(url):print("get detail url started")

time.sleep(4)print("get detail url end")if __name__ == "__main__":

thread1= threading.Thread(target=get_detail_html)

thread1= threading.Thread(target=get_detail_html)#thread1.setDaemon(True)

#thread2.setDaemon(True) # 守护线程

start_time=time.time()

thread1.start()

thread2.start()

thread1.join()

thread2.join()#线程阻塞

print ("last time: {}".format(time.time()-start_time))

2)使用thread继承方式:

推荐这种方式,我们可以在类中做更多需要的处理。

classGetDetailHtml(threading.Thread):def __init__(self, name):

super().__init__(name=name)def run(self): #重载 threading.Thread 中的方法

print("get detail html started")

time.sleep(2)print("get detail html end")classGetDetailUrl(threading.Thread):def __init__(self, name):

super().__init__(name=name)defrun(self):print("get detail url started")

time.sleep(4)print("get detail url end")if __name__ == "__main__":

thread1= GetDetailHtml("get_detail_html")

thread2= GetDetailUrl("get_detail_url")

start_time=time.time()

thread1.start()

thread2.start()

thread1.join()

thread2.join()print ("last time: {}".format(time.time()-start_time))

3、线程间通信:共享变量和 Queue

1)共享变量的方式,即全局定义一个变量,给多个子线程中调用。安全性不高,不建议使用

2)Queue 队列的方式,比较安全,推荐使用.Queue常用方法可查看源码

#通过queue的方式进行线程间同步,更安全

from queue importQueueimporttimeimportthreadingdefget_detail_html(queue):#爬取文章详情页

whileTrue:

url= queue.get() #queue get方法 阻塞

print("get detail html started")

time.sleep(2)print("get detail html end")defget_detail_url(queue):#爬取文章列表页

whileTrue:print("get detail url started")

time.sleep(4)for i in range(20):

queue.put("http://projectsedu.com/{id}".format(id=i)) #queue put 方法

print("get detail url end")if __name__ == "__main__":

detail_url_queue= Queue(maxsize=1000)

thread_detail_url= threading.Thread(target=get_detail_url, args=(detail_url_queue,))for i in range(10):

html_thread= threading.Thread(target=get_detail_html, args=(detail_url_queue,))

html_thread.start()

start_time=time.time()

detail_url_queue.task_done() 结束队列阻塞

detail_url_queue.join()#队列阻塞

print ("last time: {}".format(time.time()-start_time))

4. 线程同步:Lock、RLock

1)Lock

from threading importLock

lock=Lock()

lock.acquire()#获取锁

lock.acquire() #上面已获取锁,再acquire则会阻塞住,即在锁未释放前,两次acquire就会造成死锁

total += 1lock.release()#释放锁,在释放锁之前其他线程都会被hold住,等待锁释放

lock.release()

注意:1、用锁会影响性能

2、用锁注意造成死锁问题:

1)未释放锁前,连续两次acquire 会造成死锁

2)资源竞争:即线程一要的资源在线程二中,线程二要的资源在线程一中,线程一获取锁需要用到线程二的资源,然而锁在线程一中,线程二锁住(阻塞住)无法将资源给到线程一,因此造成死锁

2)RLock

可重入的锁 ,在同一个线程里面,可以连续调用多次acquire, 一定要注意acquire的次数要和release的次数相等

from threading importLock, RLock, Condition#RLock可重入的锁,在同一个线程里面,可以连续调用多次acquire, 一定要注意acquire的次数要和release的次数相等

total=0

lock=RLock()defadd():globallockglobaltotalfor i in range(1000000):

lock.acquire()#获取锁

lock.acquire() #RLock ,不会造成死锁

total += 1lock.release()#释放锁

lock.release() #release次数需与acquire次数一致

RLock的可用性要远远高于Lock,如果要使用锁,建议使用RLock

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

from threading importLock, RLock, Condition

total=0

lock=RLock()defadd():globallockglobaltotalfor i in range(1000000):

lock.acquire()#获取锁

lock.acquire()

total+= 1lock.release()#释放锁

lock.release()defdesc():globaltotalgloballockfor i in range(1000000):

lock.acquire()

total-= 1lock.release()importthreading

thread1= threading.Thread(target=add)

thread2= threading.Thread(target=desc)

thread1.start()

thread2.start()

thread1.join()

thread2.join()print(total)

demo

5、线程同步 - condition 使用以及源码分析

condition中实现了魔法函数:__enter__、__exit__ ,是上下文管理器,可以用with处理

95c8a4c9b57f4d2ed49807c6821e8791.png

#通过condition完成协同读诗

importthreadingclassXiaoAi(threading.Thread):def __init__(self, cond):

super().__init__(name="小爱")

self.cond=conddefrun(self):

with self.cond:

self.cond.wait()print("{} : 在".format(self.name))

self.cond.notify()

self.cond.wait()print("{} : 好啊".format(self.name))

self.cond.notify()

self.cond.wait()print("{} : 君住长江尾".format(self.name))

self.cond.notify()

self.cond.wait()print("{} : 共饮长江水".format(self.name))

self.cond.notify()

self.cond.wait()print("{} : 此恨何时已".format(self.name))

self.cond.notify()

self.cond.wait()print("{} : 定不负相思意".format(self.name))

self.cond.notify()classTianMao(threading.Thread):def __init__(self, cond):

super().__init__(name="天猫精灵")

self.cond=conddefrun(self):

with self.cond:print("{} : 小爱同学".format(self.name))

self.cond.notify()

self.cond.wait()print("{} : 我们来对古诗吧".format(self.name))

self.cond.notify()

self.cond.wait()print("{} : 我住长江头".format(self.name))

self.cond.notify()

self.cond.wait()print("{} : 日日思君不见君".format(self.name))

self.cond.notify()

self.cond.wait()print("{} : 此水几时休".format(self.name))

self.cond.notify()

self.cond.wait()print("{} : 只愿君心似我心".format(self.name))

self.cond.notify()

self.cond.wait()if __name__ == "__main__":from concurrent importfutures

cond=threading.Condition()

xiaoai=XiaoAi(cond)

tianmao=TianMao(cond)

xiaoai.start()

tianmao.start()#启动顺序很重要

#在调用with cond之后才能调用wait或者notify方法

#condition有两层锁, 一把底层锁会在线程调用了wait方法的时候释放, 上面的锁会在每次调用wait的时候分配一把并放入到cond的等待队列中,等到notify方法的唤醒

6、线程同步 - Semaphore 使用以及源码分析

Semaphore:信号量, 是用于控制进入数量的锁。本质上是锁,Lock是单锁,信号量是指定多把锁,也就是说通过信号量指定多个数线程可以访问相同资源,一般情况下读操作可以有多个,但写操作同时只有一个

Semaphore 管理一个计数器,每调用一次 acquire() 方法,计数器就减一,每调用一次 release() 方法,计数器就加一。计时器的值默认为 1 ,计数器的值不能小于 0,当计数器的值为 0 时,调用 acquire() 的线程就会等待,直到 release() 被调用。 因此,可以利用这个特性来控制线程数量

控制爬虫数量,一次最多执行3个线程:

importthreadingimporttime#控制爬虫数量,一次执行三次

classHtmlSpider(threading.Thread):def __init__(self, url, sem):

super().__init__()

self.url=url

self.sem=semdefrun(self):

time.sleep(2)print("got html text success")

self.sem.release()#Semaphore.release(),释放锁

classUrlProducer(threading.Thread):def __init__(self, sem):

super().__init__()

self.sem=semdefrun(self):for i in range(20):

self.sem.acquire()#Semaphore.acquire(),每运行一个爬虫自动减1,当3个爬虫都运行时,计数器减为零,此时再调用acquire方法则会hold

html_thread = HtmlSpider("https://baidu.com/{}".format(i), self.sem)

html_thread.start()if __name__ == "__main__":

sem= threading.Semaphore(3) #控制爬虫数量为3个

url_producer =UrlProducer(sem)

url_producer.start()

7、ThreadPoolExecutor线程池

线程池使用:不仅仅是数量控制,可以获取线程状态、任务状态、线程返回值等信息; 当一个线程完成的时候我们主线程能立即知道  ; futures可以让多线程和多进程编码接口一致 。

线程池模块  ThreadPollExecutor

线程池使用过程:

实例化线程池

提交任务,会有个返回对象,submit是不会堵塞,立即返回

让主线程等待线程执行完成

关闭线程池

线程池几个方法:

done()  :判断任务是否完成

result() :获取任务执行结果,会阻塞

cancle():取消任务,任务在执行中或者已经执行完成则无法取消

from concurrent.futures importThreadPoolExecutorimporttimedefget_html(times):

time.sleep(times)print("get page {} success".format(times))returntimes#程序执行时,会到线程池执行线程

executor = ThreadPoolExecutor(max_workers=2) #线程数最大两个#通过submit函数提交执行的函数到线程池中, submit不会阻塞,有个返回值,线程状态等可以通过这个返回值查看

task1 = executor.submit(get_html, (3))

task2= executor.submit(get_html, (2))#状态查询:#done方法用于判定某个任务是否完成#print(task1.done()) # 返回:False#print(task2.cancel()) # 可以取消没有执行的#time.sleep(3)#print(task1.done()) # 返回:True

##result方法可以获取task的执行结果#print(task1.result()) # 返回:3

1)futures下的as_completed()方法 :获取已经执行完成的任务的结果(推荐)

from concurrent.futures import as_completed

urls = [3,2,4]

all_task= [executor.submit(get_html, (url)) for url inurls]#wait(all_task, return_when=FIRST_COMPLETED)

for future inas_completed(all_task): as_completed是个生成器,执行完成的任务都能获取到

data=future.result()print("get {} page".format(data)) #打印执行成功的任务

2)线程池自带的map()方法:获取已经执行完成的任务结果

from concurrent.futures importThreadPoolExecutor, as_completed, wait, FIRST_COMPLETEDimporttimedefget_html(times):

time.sleep(times)print("get page {} success".format(times))returntimes

executor= ThreadPoolExecutor(max_workers=2)#通过executor的map获取已经完成的task的值

for data inexecutor.map(get_html, urls):print("get {} page".format(data))

3)futures下的wait()方法 :等待线程完成,才执行下面的程序

urls = [3,2,4]

all_task= [executor.submit(get_html, (url)) for url inurls]

wait(all_task, return_when=FIRST_COMPLETED) #阻塞,return_when(条件),FIRST_COMPLETED:第一个线程执行完成才能执行下面的,未完成前会阻塞住

print("main")for future inas_completed(all_task):

data=future.result()print("get {} page".format(data))

8、multiprocessing 多进程编程

1)多进程创建方式

方式一:

importmultiprocessingimporttimedefworker_1(interval):print "worker_1"time.sleep(interval)print "end worker_1"

defworker_2(interval):print "worker_2"time.sleep(interval)print "end worker_2"

defworker_3(interval):print "worker_3"time.sleep(interval)print "end worker_3"

if __name__ == "__main__":

p1= multiprocessing.Process(target = worker_1, args = (2,))

p2= multiprocessing.Process(target = worker_2, args = (3,))

p3= multiprocessing.Process(target = worker_3, args = (4,))

p1.start()

p2.start()

p3.start()print("The number of CPU is:" +str(multiprocessing.cpu_count()))for p inmultiprocessing.active_children():print("child p.name:" + p.name + "\tp.id" +str(p.pid))print "END!!!!!!!!!!!!!!!!!"

# 结果:

The number of CPU is:4

child p.name:Process-3 p.id7992

child p.name:Process-2 p.id4204

child p.name:Process-1 p.id6380

END!!!!!!!!!!!!!!!!!

worker_1

worker_3

worker_2

end worker_1

end worker_2

end worker_3

方式二:

importmultiprocessingimporttimeclassClockProcess(multiprocessing.Process):def __init__(self, interval):

multiprocessing.Process.__init__(self)

self.interval=intervaldefrun(self):

n= 5

while n >0:print("the time is {0}".format(time.ctime()))

time.sleep(self.interval)

n-= 1

if __name__ == '__main__':

p= ClockProcess(3)

p.start()

# 结果:

the time is Tue Apr 21 20:31:30 2015

the time is Tue Apr 21 20:31:33 2015

the time is Tue Apr 21 20:31:36 2015

the time is Tue Apr 21 20:31:39 2015

the time is Tue Apr 21 20:31:42 2015

2)进程池:

方式一:

from concurrent.futures importProcessPoolExecutorimportrequestsimporttimedeftask(url):

response=requests.get(url)print(url,response)#写正则表达式

pool= ProcessPoolExecutor(7)

url_list=['http://www.cnblogs.com/wupeiqi','http://huaban.com/favorite/beauty/','http://www.bing.com','http://www.zhihu.com','http://www.sina.com','http://www.baidu.com','http://www.autohome.com.cn',

]for url inurl_list:

pool.submit(task,url)

pool.shutdown(wait=True)

方式二:

from concurrent.futures importProcessPoolExecutorimportrequestsimporttimedeftask(url):

response=requests.get(url)returnresponsedef done(future,*args,**kwargs):

response=future.result()print(response.status_code,response.content)

pool= ProcessPoolExecutor(7)

url_list=['http://www.cnblogs.com/wupeiqi','http://huaban.com/favorite/beauty/','http://www.bing.com','http://www.zhihu.com','http://www.sina.com','http://www.baidu.com','http://www.autohome.com.cn',

]for url inurl_list:

v=pool.submit(task,url)

v.add_done_callback(done)

pool.shutdown(wait=True)

3)进程下的pool

使用进程池(非阻塞):

#coding: utf-8

importmultiprocessingimporttimedeffunc(msg):print "msg:", msg

time.sleep(3)print "end"

if __name__ == "__main__":

pool= multiprocessing.Pool(processes = 3)for i in xrange(4):

msg= "hello %d" %(i)

pool.apply_async(func, (msg, ))#维持执行的进程总数为processes,当一个进程执行完毕后会添加新的进程进去

print "Mark~ Mark~ Mark~~~~~~~~~~~~~~~~~~~~~~"pool.close()

pool.join()#调用join之前,先调用close函数,否则会出错。执行完close后不会有新的进程加入到pool,join函数等待所有子进程结束

print "Sub-process(es) done."

函数解释:

apply_async(func[, args[, kwds[, callback]]]) 它是非阻塞,apply(func[, args[, kwds]])是阻塞的(理解区别,看例1例2结果区别)

close()    关闭pool,使其不在接受新的任务。

terminate()    结束工作进程,不在处理未完成的任务。

join()    主进程阻塞,等待子进程的退出, join方法要在close或terminate之后使用。

执行说明:创建一个进程池pool,并设定进程的数量为3,xrange(4)会相继产生四个对象[0, 1, 2, 4],四个对象被提交到pool中,因pool指定进程数为3,所以0、1、2会直接送到进程中执行,当其中一个执行完事后才空出一个进程处理对象3,所以会出现输出“msg: hello 3”出现在"end"后。因为为非阻塞,主函数会自己执行自个的,不搭理进程的执行,所以运行完for循环后直接输出“mMsg: hark~ Mark~ Mark~~~~~~~~~~~~~~~~~~~~~~”,主程序在pool.join()处等待各个进程的结束。

使用进程池(阻塞):

#coding: utf-8

importmultiprocessingimporttimedeffunc(msg):print "msg:", msg

time.sleep(3)print "end"

if __name__ == "__main__":

pool= multiprocessing.Pool(processes = 3)for i in xrange(4):

msg= "hello %d" %(i)

pool.apply(func, (msg, ))#维持执行的进程总数为processes,当一个进程执行完毕后会添加新的进程进去

print "Mark~ Mark~ Mark~~~~~~~~~~~~~~~~~~~~~~"pool.close()

pool.join()#调用join之前,先调用close函数,否则会出错。执行完close后不会有新的进程加入到pool,join函数等待所有子进程结束

9、进程间通信 - Queue、Pipe,Manager

1)Queue队列

是 multiprocessing自带的 Queue

importtimefrom multiprocessing importProcess, Queue#from queue import Queue #不是这个Queue

defproducer(queue):

queue.put("a")

time.sleep(2)defconsumer(queue):

time.sleep(2)

data=queue.get()print(data)if __name__ == "__main__":

queue= Queue(10)

my_producer= Process(target=producer, args=(queue,))

my_consumer= Process(target=consumer, args=(queue,))

my_producer.start()

my_consumer.start()

my_producer.join()

my_consumer.join()

multiprocessing中的Queue不能用于multiprocessing下的pool进程池,pool中的进程间通信需要使用manager中的queu

目前我们已经说到了三个Queue:

from queue import Queue :用于线程通信

from multiprocessing import Queue :用于进程通信

from multiprocessing import Manager → q=Manager().Queue() :用于multiprocessing下的pool进程池的进程通信

2)Pipe 管道

multiprocessing下的Pipe ,Pipe只能用于两个进程间的通信,Pipe的性能高于Queue的

from multiprocessing importProcess, Pipedefproducer(pipe):

pipe.send("MJ")defconsumer(pipe):print(pipe.recv())if __name__ == "__main__":

recevie_pipe, send_pipe=Pipe()#pipe只能适用于两个进程间通信

my_producer= Process(target=producer, args=(send_pipe, ))

my_consumer= Process(target=consumer, args=(recevie_pipe,))

my_producer.start()

my_consumer.start()

my_producer.join()

my_consumer.join()

GIL锁知识点:

Python的多线程在多核CPU上,只对于IO密集型计算产生正面效果;而当有至少有一个CPU密集型线程存在,那么多线程效率会由于GIL而大幅下降。

即使在多核上运行多线程,同一时刻也只有一核会运行一个线程。

遇到CPU密集型这种,建议不使用多线程,而改用多进程处理,但多进程更耗成本

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值