目录
我们在进程、线程和协程(基础篇) 和 进程、线程和协程 中学习到了很多进程、线程和协程的知识,而且他们是高并发,高可用的基础。那么今天我们就学习学习在具体一门语言中的应用---python
相对于所有语言来说的,Python的特殊之处在于Python有一把GIL锁,这把锁限制了同一时间内一个进程只能有一个线程能使用cpu
线程threading模块
在python中比较底层的模块是_thread模块,而threading模块就是对_thread模块进行了封装,使之使用起来更加方便,我也推荐使用threading模块。
threading模块常用函数:
多线程的创建
from random import randint
from threading import Thread
from time import time, sleep
def download(filename):
print('开始下载%s...' % filename)
time_to_download = randint(1, 10)
sleep(time_to_download)
print('%s下载完成! 耗费了%d秒' % (filename, time_to_download))
def main():
start = time()
t1 = Thread(target=download, args=('Python入门.pdf',))
t1.start()
t2 = Thread(target=download, args=('Python精通.pdf',))
t2.start()
t1.join()
t2.join()
end = time()
print('总共耗费了%.3f秒' % (end - start))
if __name__ == '__main__':
main()
运行结果:
我们使用了多线程实现多任务下载,这是通过直接使用threading模块的函数实现的,其实我们也可以通过继承threading.Thread方式实现,如下:
from random import randint
from threading import Thread
from time import time, sleep
class DownloadTask(Thread):
def __init__(self, filename):
super().__init__()
self._filename = filename
def run(self):
print('开始下载%s...' % self._filename)
time_to_download = randint(1, 10)
sleep(time_to_download)
print('%s下载完成! 耗费了%d秒' % (self._filename, time_to_download))
def main():
start = time()
t1 = DownloadTask('Python入门.pdf')
t1.start()
t2 = DownloadTask('Python精通.pdf')
t2.start()
t1.join()
t2.join()
end = time()
print('总共耗费了%.2f秒.' % (end - start))
if __name__ == '__main__':
main()
线程安全
我们从进程、线程和协程中知道如果要使用多线程必然会有线程安全问题,那么python中有哪些具体的实现来处理这个问题呢,如下会慢慢举例说明.
同步锁(用户锁,互斥锁)
import threading
import time
def sub():
global num
lock.acquire()#获取锁
temp=num
time.sleep(0.001)
num=temp-1
lock.release()#释放锁
time.sleep(2)
num=100
l=[]
lock=threading.Lock()
for i in range(50):
t=threading.Thread(target=sub,args=())
t.start()
l.append(t)
for i in l:
i.join()
print(num)
我们在使用锁的时候需要注意不要出现死锁(两个及以上进程或线程在执行过程中,因相互制约造成的一种互相等待的现象)的现象。
递归锁/可重用锁(RLock)
import threading,time
class MyThread(threading.Thread):
def __init(self):
threading.Thread.__init__(self)
def run(self):
self.foo()
self.bar()
def foo(self):
RLock.acquire()
print('i am %s GET LOCKA'%(self.name))
RLock.acquire()
print('i am %s GET LOCKB'%(self.name))
RLock.release()
time.sleep(1)
RLock.release()
def bar(self):#与
RLock.acquire()
print('i am %s GET LOCKB'%(self.name))
RLock.acquire()
print('i am %s GET LOCKA'%(self.name))
RLock.release()
RLock.release()
RLock=threading.RLock()
for i in range(10):
t=MyThread()
t.start()
运行结果:
线程池
系统启动一个新线程的成本是比较高的,在这种情形下,使用线程池可以很好地提升性能。在python中,线程池的基类是 concurrent.futures 模块中的 Executor,Executor 提供了两个子类,即 ThreadPoolExecutor 和 ProcessPoolExecutor,其中 ThreadPoolExecutor 用于创建线程池,而 ProcessPoolExecutor 用于创建进程池。如果使用线程池/进程池来管理并发编程,那么只要将相应的 task 函数提交给线程池/进程池,剩下的事情就由线程池/进程池来搞定。
程序将 task 函数提交(submit)给线程池后,submit 方法会返回一个 Future 对象,Future 类主要用于获取线程任务函数的返回值。由于线程任务会在新线程中以异步方式执行,因此,线程执行的函数相当于一个“将来完成”的任务,所以 Python 使用 Future 来代表。
常用函数:
例子如下:
import random
import time
from concurrent.futures import ThreadPoolExecutor
from threading import Thread
def download(*, filename):
start = time.time()
print(f'开始下载 {filename}.')
time.sleep(random.randint(1, 10))
print(f'{filename} 下载完成.')
end = time.time()
print(f'下载耗时: {end - start:.3f}秒.')
def main():
with ThreadPoolExecutor(max_workers=4) as pool:
filenames = ['Python入门.pdf', 'Python精通.pdf', 'Linux精通.pdf']
start = time.time()
for filename in filenames:
pool.submit(download, filename=filename)
end = time.time()
print(f'总耗时: {end - start:.3f}秒.')
if __name__ == '__main__':
main()
运行结果如下:
多进程multiprocessing模块
python 中有一把全局锁(GIL)使得多线程无法使用多核,但是如果是多进程,这把锁就限制不了了。如何开多个进程呢,需要导入一个multiprocessing模块
常用函数:
例子代码如下:
from multiprocessing import Pool
import time
def foo(n):
print(n)
time.sleep(2)
if __name__ == '__main__':
pool_obj=Pool(3)#创建进程池
filenames = ['Python入门.pdf', 'Python精通.pdf', 'Linux精通.pdf']
#通过进程池创建进程
for i in range(3):
p=pool_obj.apply_async(func=foo,args=(filenames[i],))
#p是创建的池对象
# pool 的使用是先close(),在join(),记住就行了
pool_obj.close()
pool_obj.join()
协程
yield
yield是个挺神奇的东西,这是Python的一个特点。一般的函数,是遇到return就停止,然后返回return 后面的值,默认是None,yield和return很像,但是遇到yield不会立刻停止,而是暂停住,直到遇到next(),(for循环的原理也是next()) 才会继续执行。yield 前面还可以跟一个变量,通过send()函数给yield传值,把值保存在yield前边的变量中。
如下:
import time
def work1():
for i in range(5):
time.sleep(0.1)
print("---work1----{}".format(i))
yield
def work2():
for i in range(5):
time.sleep(0.1)
print("---work2----{}".format(i))
yield
# 创建两个生成器对象,实现多任务
g1 = work1()
g2 = work2()
while True:
try:
next(g1)
next(g2)
except StopIteration:
break
运行结果如下:
gevent模块
安装:
pip install gevent
函数:
gevent.spawn(*args, **kwargs); //创建并开启协程
gevent.sleep(); //切换的标志
gevent().join(); //线程等待协程的方法
gevent().joinall(); //线程等待协程的方法
import requests
import gevent
import time
def foo(url):
response=requests.get(url)
response_str=response.text
print('get data %s'%len(response_str))
s=time.time()
gevent.joinall([gevent.spawn(foo,"https://www.zhihu.com/"),
gevent.spawn(foo, "https://www.baidu.com/?tn=87135040_5_oem_dg"),])
print(time.time()-s)
总结
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持@!