聚沙成塔--爬虫系列(十四)(群架要怎么打)

版权声明:本文为作者原创文章,可以随意转载,但必须在明确位置标明出处!!!

tips:本基础系列旨在以爬虫带大家入门Python语言

本章并不是要教你如何去打群架,本篇文章将主要介绍如何使用多线程来帮我们的提高程序的执行效率,「群架」只是为了更加生动的去描述多线程的现象,相信大部分读者都看过《叶问》,叶问为了在日本军那里拿到粮食它决定一次要打10个,这种模式就是单线程模式,他必须把10个人一个个打败了才能赢得粮食,这种模式也是我们前面章节用到的模式,虽然最终能够赢得胜利,但用时就比较长了。假设叶问有多只手,每次最多只能用两只手,那么他在和人对战的时候就可以出其不意的将对手干翻掉,这肯定不两只手解决敌人的速度要块。这就是多线程模式,由于Python解释器GIL(Global Interpreter Lock)的存在,同一时刻只能有一个线程在执行,所以对于现在的多核CPU而言,Python中的多线程并没提高程序的处理能力,反而有可能降低程序的处理能力,如果你开启的线程足够多的话,线程的上下文切换将会造成程序的执行效率变低。

那么有的读者就会问既然Python的多线程设计并不能有效的提高程序的处理能力,那为什么还要有这个模块呢,说到这里就需要给读者普及一下概念了,I/O密集型、CPU密集型

I/O密集型

什么是I/O密集型呢,I/O的是输入/输出(input/output)的意思,就是我们程序大部分的时间都是用来等待I/O操作,想网络的请求、文件的读写、数据库的读写都属于I/O操作,前面的章节说过I/O操作是很费时的。所以Python中的多线程适用于I/O密集型的任务,因为它不需要用到CPU的计算能力。

CPU密集型

CPU密集型的意思就是计算密集型,如果你的程序中有大量复杂的计算逻辑那么就选择CPU密集型,因为复杂计算是十分耗CPU资源的,想圆周率的计算,高清视频数据的处理等等。那么Python中怎么设计CPU密集型呢,当然是多进程的使用,什么意思呢,就拿上面叶问的例子,若是叶问有10个分身,二期10个分身都是独立有思想的,那么打10个还不是分分钟的事吗。

threading模块

多线程有两个模块一个是thread模块、一个就是threading模块,这里推荐大家使用threading模块,不推荐使用thread模块有两个原因,一个是它对于进程何时退出没有控制,当主线程结束时子线程也会被强制性的结束也不会发出警告或者进行适当的清理和释放工作。另一个原因是该模块不支持守护线程这个概念。这里普及一个概念,线程是不能单独执行的,它必须依赖进程才能存活。进程至少包含一个线程也就是上面说道的主线程,只要进程启动,那么主线程也就启动了。

threading模块对象

threading模块提供了如下对象:


hreading模块对象

这些对象都包含在threading模块里。没个类对象都提供了哪些方法接口都可以在开放这文档查看到。


创建线程

使用Thread类来创建线程,创建线程有有几种方式,你可以选择你最舒服最中意的方式去使用。

  • 创建Thread的实例,传给它一个参数
import threading
def fun():
    count = 0
    for index in range(10000000):
        count = count + 1
th = threading.Thread(target=fun, args=())
    th.start()
    th.join()
  • 创建Thread 的实例,传给它一个可调用的类实例
import threading
class ThreadFunc(object):
    def __init__(self, func, args):    
        self.func = func
        self.args = args
    def __call__(self):
        self.res = self.func(*self.args)

def fun():
    count = 0
    for index in range(10000000):
        count = count + 1
th = threading.Thread(target = ThreadFunc(fun, ())
th.start()
th.join()
  • 派生Thread 的子类,并创建子类的实例
import threading
class myThread(threading.Thread):
    def __init__(self, func, args):
        super(myThread, self).__init__(self)
        self.args     = args
        self.func     = func
    def run(self):        
        self.res = self.func(*self.args)

def fun():
    count = 0
    for index in range(10000000):
        count = count + 1
th= myThread(fun, ())
th.start()
th.join()

推荐使用第三种方式,第三种方式对于灵活行和未来的扩展性更好。

同步

涉及到多线程编程必定要涉及到一个主题就是数据同步,这里普及一下多线程为什么能提高程序的处理能力,这个跟CPU的工作原理有关,只有拿到CPU分给线程执行的时间片,我们的代码才能够被CPU执行,所以如果我们有多个线程去执行同一个任务那么每个线程有是有机会得到CPU时间片,那对于处理同一个人才得到的时间片总和就比单个线程大太多了,所以多线程能够提高程序的处理能力。但是多线程的处理会遇到数据同步的问题,为什么会出现这种问题呢,我们知道了线程只有得到CPU时间片才能执行,那么当A线程正在执行还没有执行完,这个时候CPU时间片到了,那么A线程就会暂停在此刻并进入「休眠」状态,B线程拿到时间片也执行同样的动作,这个时候B就有可能把A、B线程共同拥有的数据给破坏掉,举个例子,张三、李四都喜欢吃热狗,他们一起走进店里买热狗,张三刚刚想把热狗拿起来,这个时候由于CPU时间到了,张三就进入休眠状态了,李四刚好拿到CPU时间片他就把热狗拿走了,等到下一次张三拿到CPU时间片从休眠状态中唤醒,他认为自已已经拿到热狗了,但真实的情况是这根热狗已经被李四拿走了所以这就造成了数据的不同步了。讲了这么多我们来看一个卖票的例子,代码如下:

import threading
tickets = 10000000
listdata = []

def sale_ticket():
    global tickets
    while tickets:
        tickets = tickets - 1
        # print(tickets)
        listdata.append(tickets)

threads = []

for index in range(10):
    thread = threading.Thread(target=sale_ticket, args=())
    thread.start()
    threads.append(thread)
    

for t in threads:
    t.join()

print(len(listdata))
print('thread exit')

#执行结果:33934146

从结果中我们可以看出这个结构肯定是不对的,因为我们只有10000000这么多张票,而结果是我们总票数的3倍还多,这就是因为有很多人都拿到了相同的票。那么如何解决数据同步呢,解决数据同步的很多总方法,章节前面threading模块对象已经列出来了,Lock、RLock等,下面我们主要讲一下Lock的用法,

Lock类

Lock类提供了两个方法

  • acquire(blocking=True, timeout=-1)
    获取一个锁 blocking参数默认为True,即阻塞模式,什么意思呢,就是电话亭一样,如果电话亭里有人在打电话那么下一个人只有等电话亭里的人打完电话你才能打,如果设置为False,既不阻塞,如果电话亭里有人打电话那我就干其它事去了,等会儿再来看看,若是电话亭里这个时候没有人我就把电话亭外挂个请勿打扰的牌子,timeout超时时间,只有blocking为True有用。
  • released()
    该函数没有参数,释放一个锁

加锁改写后的代码

import threading
tickets = 10000000
listdata = []
mutex = threading.Lock()

def sale_ticket():
    global tickets
    while tickets:
        if mutex.acquire():
            tickets = tickets - 1
            # print(tickets)
            listdata.append(tickets)
        mutex.release()

threads = []

for index in range(10):
    thread = threading.Thread(target=sale_ticket, args=())
    thread.start()
    threads.append(thread)
    

for t in threads:
    t.join()

print(len(listdata))
print('thread exit')

# 执行结果:10000000

从结果可以看出我们保证了数据的同步,也就是保证了每个人都买到的票都是唯一的。

okay,本章就讲到这里就结束了, 因为我们的爬虫程序正好是一个I/O密集型程序,所以使用多线程设计正好合适,如何设计多线程我将放到后面的章节将,读者有兴趣的可以去我的git查看源码,地址https://github.com/Gavinxyj/Python/tree/master/python_study/Scrapy/modules欢迎大家fork、star。

PS:成为牛人你只需要保持每天进步一点点


欢迎关注我:「爱做饭的老谢」,老谢一直在努力...

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容