python的多线程与多进程

1.GIL(Global Interpreter Lock)

在python中,由于GIL(Global Interpreter Lock)全局解释器锁的存在,所以多线程并不是真正意义上的多线程。在 Python 语言的主流实现 CPython 中,GIL 是一个货真价实的全局线程锁,在解释器解释执行任何 Python 代码时,都需要先获得这把锁才行。CPython 进程做为一个整体,同一时间只会有一个获得了 GIL 的线程在跑,其它的线程都处
于等待状态等着 GIL 的释放。
通俗来讲就是:你现在面前放了三台电脑,你在第一台电脑上敲了一句代码,电脑开始执行任务,紧接着你又在第二台电脑上开了一个音乐播放器,播放音乐,再然后,你又切换到了第三台电脑,开始打开 python 的学习视频观看,在那些不懂电脑人的眼里,你就像是同时在使用三台电脑。线程锁好比就是你这个人,而至始至终只有你这一个人在操控电脑,真正意义上的多线程应该是三个人同时用三个电脑进行工作。

2.线程

但是虽然如此,使用多线程仍然能够起到加速的作用。
那什么是线程呢?
以电脑中的软件举例,我们每运行一个软件,就是开启了一个进程。比如我们想听一首歌,所以打开了网易云音乐,此时就是开启了一个进程。偶尔碰到一首歌曲,非常好听,想下载下来,在我们下载歌曲时,歌曲的播放并不会停止,这就是使用多线程来解决的。一个程序中的多任务执行就是多线程。且一个进程中至少包含一个线程。

线程的概念

由于进程是资源拥有者,创建、撤消与切换存在较大的内存开销,因此需要引入轻型进程即线程,进程是资源分配的最小单位,线程是 CPU 调度的最小单位(程序真正执行的时候调用的是线程).每一个进程中至少有一个线程。

线程的创建

分两种,实例化对象开启线程与函数开启线程
函数开启线程:
引入模块:from threading import Thread
创建对象:t = Thread(target=func,args=(arg1,))
实例化对象开启:
引入模块:from threading import Thread
创建对象:t = Classname(arg1,arg2)
参数的含义:

  • target:如果传递了函数的引用,可以让这个子进程就执行函数中的代码,值为函数名
  • args:给 target指定的函数传递的参数,以元组的形式进行传递
  • kwargs:给 target 指定的函数传递参数,以字典的形式进行传递
  • name:给线程设定一个名字,可以省略
    常用方法:
    t.activeCount():获取当前总共线程的个数,包含主线程
    t.enumerate():返回当前运行中的 Thread 对象列表
    t.setdaemon(True):设置守护线程,默认为子线程
    t.start():开启线程
    t.is_alive():判断某个线程是否存活,返回布尔值
    t.join():进行阻塞,等当前线程执行完毕之后再执行下面的代码
线程间通信

由于线程是在同一个进程中存在的,所以难免会遇到共享变量的情况,在这个时候,可以使用队列来达到目的,而且非常好用,可以避免重复。
导入:from threading import Queue
对象:q = Queue(n)
其中n是指定的队列长度,可以不进行指定
q.put()
将变量放入队列中
q.get()
将变量从队列中取出

线程锁(互斥锁)

导入:from threading import Lock
对象:lock = Lock()
作用:一般放在多线程共享的变量更改时的上下使用。在某个线程进行变量更改时可以防止其他线程对此变量进行更改,常见举例,购物车
上锁:lock.acquire() #此时别的线程无法对变量进行更改
解锁:lock.release()

案例:
from queue import Queue
from threading import Thread
import requests,json,time,threading
base_url = 'https://careers.tencent.com/tencentcareer/api/post/Query?timestamp=1562211235205&countryId=&' \
           'cityId=&bgIds=&productId=&categoryId=&parentCategoryId=&attrId=&keyword=python&pageIndex={}' \
           '&pageSize=10&language=zh-cn&area=cn'
headers = {'user-agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:67.0) Gecko/20100101 Firefox/67.0'}
"""使用函数开启多线程"""
def thread1(q1,q2,i):
    # 如果不确定具体次数可以进行无限循环,并设置退出条件
    while True:
        try:
            url = q1.get(timeout=2)#timeout的作用是设置等待时间,如果超过等待时间便继续下次循环,不会报错
            response = requests.get(url=url, headers=headers).content.decode('utf-8')
            contents = json.loads(response)
            # 将变量放入队列
            q2.put(contents)
            print('url线程'+str(i)+'放了一个内容进去')
        except:
            # 设置退出条件,当队列中没有数据时退出,但这种方式不严谨,不建议使用
            if q1.empty():
                break
def thread4(q2,i):
    while True:
        try:
            # 无限从队列中取出变量,并设置等待时间
            contents = q2.get(timeout=1)
            print('解析线程' + str(i) + '解析了一个内容'+'----->'+contents['Data']['Posts'][0]['RecruitPostName'])
            print('*******'*10)
        except:
            if con:
                print('再等会')
                continue
            else:
                print('搞定,收工')
                # 查看当前活动的线程
                print(threading.activeCount())
                break


if __name__ == '__main__':
    # 创建队列对象
    q1 = Queue()
    q2 = Queue()
    con = False
    for i in range(1,75):
        url = base_url.format(i)
        q1.put(url)
    t_list = []
    for  i in range(1,4):
        # 创建线程
        t1 = Thread(target=thread1,args=(q1,q2,i))
        t4 = Thread(target=thread4,args=(q2,i))#注意是元组,要加逗号
        # 开启线程
        t1.start()
        t4.start()
        t_list.append(t1)
        # t1.join()
    # 对每一个放数据的线程都进行阻塞
    for t in t_list:
        t.join()
    con = True

"""使用类的方法开启多线程
与函数开启没有太大区别,只是创建方式不同,不过类较为正式
"""
class Produce(Thread):#继承Thread类
    def __init__(self,name,url_q,data_q):
        super(Produce, self).__init__()
        self.name = name
        self.url_q = url_q
        self.data_q = data_q
    def run(self):
        while True:
            try:
                url = self.url_q.get(timeout=1)
                contents = requests.get(url=url,headers=headers).json()
                self.data_q.put(contents)
                print(self.name+'*************************发布了一个招聘公告')
            except:
                break

class Consumer(Thread):
    def __init__(self,name,data_q):
        super(Consumer, self).__init__()
        self.name = name
        self.data_q = data_q
    def run(self):
        while True:
            try:
                contents = self.data_q.get(timeout=1)
                print(self.name+'来应聘----->>'+contents['Data']['Posts'][0]['RecruitPostName'])
            except:
                # 由于设置了阻塞,所以在con的值为True时说明整个程序运行结束,可以退出
                if con:
                    break
if __name__ == '__main__':
    url_q = Queue()
    data_q = Queue()
    hr = ['张三','李四','王五']
    em = ['赵六','陈七','周八']
    con = False
    p_list = []
    for i in range(1,75):
        url = base_url.format(i)
        url_q.put(url)
    for h in hr:
        p = Produce(h,url_q,data_q)
        p.start()
        p_list.append(p)
    for e in em:
        p2 = Consumer(e,data_q)
        p2.start()
    for p in p_list:
        p.join()
    con = True

3.多进程

进程的创建

与线程的创建没有太大区别
引入模块:from multiprocessing import Process
创建对象:p= Process(target=func,args=(arg1,))

参数的含义:

  • target:如果传递了函数的引用,可以让这个子进程就执行函数中的代码
  • args:给 target 指定的函数传递的参数,以元组的形式进行传递
  • kwargs:给 target 指定的函数传递参数,以字典的形式进行传递
  • name:给进程设定一个名字,可以省略
  • group:指定进程组,大多数情况下用不到

常用方法:
p.start():启动子进程实例(创建子进程)
p.is_alive():判断进程子进程是否还在活着
p.terminate():不管任务是否完成,立即终止子进程Process 创建的实例对象的常用属性:
p.name:当前进程的别名,默认为 Process-N,N 为从 1 开始递增的整数
p.pid:当前进程的 pid(进程号)

进程间通信

线程之间可以进行通信,同样的,进程之前也可以进行通信,同样也是使用队列,但是队列对象的来源不同。
导入:from multiprocessing import Queue
对象:q = Queue()
q.put()
将变量放入队列中
q.get()
将变量从队列中取出
q.empty()
如果调用此方法时 q 为空,返回 True
q.full()
如果 q 已满,返回为 True

小案例:

def getData(q):
    for i in range(1000):
        q.put(i)
        print('put')
    print('爬取完毕')
def process_data(q):
    for i in range(1000):
        r = q.get()
        print('get')
    print('处理完毕')
if __name__ == '__main__':
    q = Queue()
    p1 = Process(target=getData, args=(q,))
    p2 = Process(target=process_data, args=(q,))
    p1.start()
    p2.start()
进程池的使用

在利用Python进行系统管理,并行操作可以节约大量的时间。 当被操作对象数目不大时,可以直接利用multiprocessing中的Process动态成生多个进程, 十几个还好,但如果是上百个,上千个目标,手动的去限制进程数量却又太过繁琐, 此时可以发挥进程池的功效。 Pool可以提供指定数量的进程供用户调用,当有新的请求提交到pool中时, 如果池还没有满,那么就会创建一个新的进程用来执行该请求; 但如果池中的进程数已经达到规定最大值,那么该请求就会等待, 直到池中有进程结束,才会创建新的进程来它

导入:
from multiprocessing import Pool
创建:
pool = Pool(n)
可以指定进程池的大小
同步操作:
pool.apply(func[, args[, kwds]])
它会阻塞,直到结果就绪
异步操作:
pool.apply_async(func[, args[, kwds[, callback[, error_callback]]]])
不会阻塞,可以指定回调函数
关闭进程池:
pool.colse()
阻塞:
pool.join()
对Pool对象调用join()方法会等待所有子进程执行完毕,调用join()之前必须先调用close(),调用close()之后就不能继续添加新的Process了

小案例:

from multiprocessing import Pool
import time
def down_load(movie_name):
    for i in range(5):
        print('电影:{},下载进度{}%'.format(movie_name, (i / 4 * 100)))
    time.sleep(1)
    return movie_name
def alert(movie_name):
    print('恭喜{}下载完成了...'.format(movie_name))
if __name__ == '__main__':
    movie_lst = ['魔兽世界', '权利的游戏', '西部世界','碟中谍','战狼','红海行动','唐伯虎点秋香']
    pool = Pool(3)
    for movie_name in movie_lst:
        pool.apply_async(down_load, (movie_name,),
                         callback=alert)
    pool.close()
    pool.join()

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值