python的进程、线程和协程

目录

线程threading模块

多线程的创建

线程安全

同步锁(用户锁,互斥锁)

递归锁/可重用锁(RLock)

线程池

多进程multiprocessing模块

协程

yield

 gevent模块

总结


我们在进程、线程和协程(基础篇) 和 进程、线程和协程 中学习到了很多进程、线程和协程的知识,而且他们是高并发,高可用的基础。那么今天我们就学习学习在具体一门语言中的应用---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)

总结

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持@!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Yi Ian

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值