Python 爬虫进阶五之多线程的用法

我们之前写的爬虫都是单个线程的?这怎么够?一旦一个地方卡到不动了,那不就永远等待下去了?为此我们可以使用多线程或者多进程来处理。 首先声明一点! 多线程和多进程是不一样的!一个是 thread 库,一个是 multiprocessing 库。而多线程 thread 在 Python 里面被称作鸡肋的存在!而没错!本节介绍的是就是这个库 thread。 不建议你用这个,不过还是介绍下了,如果想看可以看看下面,不想浪费时间直接看 multiprocessing 多进程

鸡肋点

背景

  1. GIL 是什么? GIL 的全称是 Global Interpreter Lock (全局解释器锁),来源是 python 设计之初的考虑,为了数据安全所做的决定。
  2. 每个 CPU 在同一时间只能执行一个线程(在单核 CPU 下的多线程其实都只是并发,不是并行,并发和并行从宏观上来讲都是同时处理多路请求的概念。但并发和并行又有区别,并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔内发生。)

在 Python 多线程下,每个线程的执行方式:

  • 获取 GIL
  • 执行代码直到 sleep 或者是 python 虚拟机将其挂起。
  • 释放 GIL

可见,某个线程想要执行,必须先拿到 GIL,我们可以把 GIL 看作是 “通行证”,并且在一个 python 进程中,GIL 只有一个。拿不到通行证的线程,就不允许进入 CPU 执行。 在 Python2.x 里,GIL 的释放逻辑是当前线程遇见 IO 操作或者 ticks 计数达到 100(ticks 可以看作是 Python 自身的一个计数器,专门做用于 GIL,每次释放后归零,这个计数可以通过 sys.setcheckinterval 来调整),进行释放。 而每次释放 GIL 锁,线程进行锁竞争、切换线程,会消耗资源。并且由于 GIL 锁存在,python 里一个进程永远只能同时执行一个线程 (拿到 GIL 的线程才能执行),这就是为什么在多核 CPU 上,python 的多线程效率并不高。
那么是不是 python 的多线程就完全没用了呢?
在这里我们进行分类讨论:

  1. CPU 密集型代码 (各种循环处理、计数等等),在这种情况下,由于计算工作多,ticks 计数很快就会达到阈值,然后触发 GIL 的释放与再竞争(多个线程来回切换当然是需要消耗资源的),所以 python 下的多线程对 CPU 密集型代码并不友好。
  2. IO 密集型代码 (文件处理、网络爬虫等),多线程能够有效提升效率 (单线程下有 IO 操作会进行 IO 等待,造成不必要的时间浪费,而开启多线程能在线程 A 等待时,自动切换到线程 B,可以不浪费 CPU 的资源,从而能提升程序执行效率)。所以 python 的多线程对 IO 密集型代码比较友好。

而在 python3.x 中,GIL 不使用 ticks 计数,改为使用计时器(执行时间达到阈值后,当前线程释放 GIL),这样对 CPU 密集型程序更加友好,但依然没有解决 GIL 导致的同一时间只能执行一个线程的问题,所以效率依然不尽如人意。
多核性能
多核多线程比单核多线程更差,原因是单核下多线程,每次释放 GIL,唤醒的那个线程都能获取到 GIL 锁,所以能够无缝执行,但多核下,CPU0 释放 GIL 后,其他 CPU 上的线程都会进行竞争,但 GIL 可能会马上又被 CPU0 拿到,导致其他几个 CPU 上被唤醒后的线程会醒着等待到切换时间后又进入待调度状态,这样会造成线程颠簸 (thrashing),导致效率更低
多进程为什么不会这样?
每个进程有各自独立的 GIL,互不干扰,这样就可以真正意义上的并行执行,所以在 python 中,多进程的执行效率优于多线程 (仅仅针对多核 CPU 而言)。 所以在这里说结论:多核下,想做并行提升效率,比较通用的方法是使用多进程,能够有效提高执行效率。 所以,如果不想浪费时间,可以直接看多进程。

线程模块

Python通过两个标准库thread和threading提供对线程的支持。thread提供了低级别的、原始的线程以及一个简单的锁。

threading 模块提供的其他方法:

  • threading.currentThread(): 返回当前的线程变量。
  • threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
  • threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
    除了使用方法外,线程模块同样提供了Thread类来处理线程,

Thread类提供了以下方法:

  • run(): 用以表示线程活动的方法。
  • start():启动线程活动。
  • join([time]): 等待至线程中止。这阻塞调用线程直至线程的join() 方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生。
  • isAlive(): 返回线程是否活动的。
  • getName(): 返回线程名。
  • setName(): 设置线程名。

直接利用函数创建多线程

Python 中使用线程有两种方式:函数或者用类来包装线程对象。

函数式:调用 thread 模块中的 start_new_thread () 函数来产生新线程。语法如下:

thread.start_new_thread(function, args[, kwargs])

参数说明:

  • function - 线程函数。
  • args - 传递给线程函数的参数,他必须是个 tuple 类型。
  • kwargs - 可选参数。

先用一个实例感受一下:

# -*- coding: UTF-8 -*-import thread
import time# 为线程定义一个函数
def print_time(threadName, delay):count = 0while count < 5:time.sleep(delay)count += 1print "%s: %s" % (threadName, time.ctime(time.time()))# 创建两个线程
try:thread.start_new_thread(print_time, ("Thread-1", 2,))thread.start_new_thread(print_time, ("Thread-2", 4,))
except:print "Error: unable to start thread"while 1:pass
print "Main Finished"
Thread-1: Thu Nov  3 16:43:01 2016
Thread-2: Thu Nov  3 16:43:03 2016
Thread-1: Thu Nov  3 16:43:03 2016
Thread-1: Thu Nov  3 16:43:05 2016
Thread-2: Thu Nov  3 16:43:07 2016
Thread-1: Thu Nov  3 16:43:07 2016
Thread-1: Thu Nov  3 16:43:09 2016
Thread-2: Thu Nov  3 16:43:11 2016
Thread-2: Thu Nov  3 16:43:15 2016
Thread-2: Thu Nov  3 16:43:19 2016

使用 Threading 模块创建线程

使用 Threading 模块创建线程,直接从 threading.Thread 继承,然后重写 init 方法和 run 方法:

import threading
import time
import threadexitFlag = 0
class myThread (threading.Thread):   #继承父类threading.Threaddef __init__(self, threadID, name, counter):threading.Thread.__init__(self)self.threadID = threadIDself.name = nameself.counter = counterdef run(self):                   #把要执行的代码写到run函数里面 线程在创建后会直接运行run函数print "Starting " + self.nameprint_time(self.name, self.counter, 5)print "Exiting " + self.namedef print_time(threadName, delay, counter):while counter:if exitFlag:thread.exit()time.sleep(delay)print "%s: %s" % (threadName, time.ctime(time.time()))counter -= 1# 创建新线程
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)# 开启线程
thread1.start()
thread2.start()
print "Exiting Main Thread"
Starting Thread-1
Starting Thread-2Exiting Main ThreadThread-1: Mon Jan  4 17:13:08 2021
Thread-2: Mon Jan  4 17:13:09 2021
Thread-1: Mon Jan  4 17:13:09 2021
Thread-1: Mon Jan  4 17:13:10 2021
Thread-2: Mon Jan  4 17:13:11 2021
Thread-1: Mon Jan  4 17:13:11 2021
Thread-1: Mon Jan  4 17:13:12 2021
Exiting Thread-1
Thread-2: Mon Jan  4 17:13:13 2021
Thread-2: Mon Jan  4 17:13:15 2021
Thread-2: Mon Jan  4 17:13:17 2021
Exiting Thread-2

有没有发现什么奇怪的地方?打印的输出格式好奇怪。比如第一行之后应该是一个回车的,结果第二个进程就打印出来了。 那是因为什么?因为这几个线程没有设置同步。

线程同步

如果多个线程共同对某个数据修改,则可能出现不可预料的结果,为了保证数据的正确性,需要对多个线程进行同步。
使用 Thread 对象的 Lock 和 Rlock 可以实现简单的线程同步,这两个对象都有 acquire 方法和 release 方法,对于那些需要每次只允许一个线程操作的数据,可以将其操作放到 acquire 和 release 方法之间。如下: 多线程的优势在于可以同时运行多个任务(至少感觉起来是这样)。但是当线程需要共享数据时,可能存在数据不同步的问题。 考虑这样一种情况:一个列表里所有元素都是 0,线程”set” 从后向前把所有元素改成 1,而线程”print” 负责从前往后读取列表并打印。 那么,可能线程”set” 开始改的时候,线程”print” 便来打印列表了,输出就成了一半 0 一半 1,这就是数据的不同步。为了避免这种情况,引入了锁的概念。 锁有两种状态 —— 锁定和未锁定。每当一个线程比如”set” 要访问共享数据时,必须先获得锁定;如果已经有别的线程比如”print” 获得锁定了,那么就让线程”set” 暂停,也就是同步阻塞;等到线程”print” 访问完毕,释放锁以后,再让线程”set” 继续。 经过这样的处理,打印列表时要么全部输出 0,要么全部输出 1,不会再出现一半 0 一半 1 的尴尬场面。 看下面的例子:

import threading
import timeclass myThread (threading.Thread):def __init__(self, threadID, name, counter):threading.Thread.__init__(self)self.threadID = threadIDself.name = nameself.counter = counterdef run(self):print "Starting " + self.name# 获得锁,成功获得锁定后返回True# 可选的timeout参数不填时将一直阻塞直到获得锁定# 否则超时后将返回FalsethreadLock.acquire()print_time(self.name, self.counter, 3)# 释放锁threadLock.release()def print_time(threadName, delay, counter):while counter:time.sleep(delay)print "%s: %s" % (threadName, time.ctime(time.time()))counter -= 1threadLock = threading.Lock()
threads = []# 创建新线程
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)# 开启新线程
thread1.start()
thread2.start()# 添加线程到线程列表
threads.append(thread1)
threads.append(thread2)# 等待所有线程完成
for t in threads:t.join()print "Exiting Main Thread"
Starting Thread-1
Starting Thread-2
Thread-1: Thu Nov  3 18:56:49 2016
Thread-1: Thu Nov  3 18:56:50 2016
Thread-1: Thu Nov  3 18:56:51 2016
Thread-2: Thu Nov  3 18:56:53 2016
Thread-2: Thu Nov  3 18:56:55 2016
Thread-2: Thu Nov  3 18:56:57 2016
Exiting Main Thread

线程优先级队列

Python 的 Queue 模块中提供了同步的、线程安全的队列类,包括 FIFO(先入先出) 队列 Queue,LIFO(后入先出)队列 LifoQueue,和优先级队列 PriorityQueue。这些队列都实现了锁原语,能够在多线程中直接使用。可以使用队列来实现线程间的同步。

  • Queue 模块中的常用方法:
  • Queue.qsize () 返回队列的大小
  • Queue.empty () 如果队列为空,返回 True, 反之 False
  • Queue.full () 如果队列满了,返回 True, 反之 False
  • Queue.full 与 maxsize 大小对应
  • Queue.get ([block [, timeout]]) 获取队列,timeout 等待时间
  • Queue.get_nowait () 相当 Queue.get (False)
  • Queue.put (item) 写入队列,timeout 等待时间
  • Queue.put_nowait (item) 相当 Queue.put (item, False)
  • Queue.task_done () 在完成一项工作之后,Queue.task_done () 函数向任务已经完成的队列发送一个信号
  • Queue.join () 实际上意味着等到队列为空,再执行别的操作
import Queue
import threading
import timeexitFlag = 0class myThread (threading.Thread):def __init__(self, threadID, name, q):threading.Thread.__init__(self)self.threadID = threadIDself.name = nameself.q = qdef run(self):print "Starting " + self.nameprocess_data(self.name, self.q)print "Exiting " + self.namedef process_data(threadName, q):while not exitFlag:queueLock.acquire()if not workQueue.empty():data = q.get()queueLock.release()print "%s processing %s" % (threadName, data)else:queueLock.release()time.sleep(1)threadList = ["Thread-1", "Thread-2", "Thread-3"]
nameList = ["One", "Two", "Three", "Four", "Five"]
queueLock = threading.Lock()
workQueue = Queue.Queue(10)
threads = []
threadID = 1# 创建新线程
for tName in threadList:thread = myThread(threadID, tName, workQueue)thread.start()threads.append(thread)threadID += 1# 填充队列
queueLock.acquire()
for word in nameList:workQueue.put(word)
queueLock.release()# 等待队列清空
while not workQueue.empty():pass# 通知线程是时候退出
exitFlag = 1# 等待所有线程完成
for t in threads:t.join()
print "Exiting Main Thread"
Starting Thread-1
Starting Thread-2
Starting Thread-3
Thread-3 processing One
Thread-1 processing Two
Thread-2 processing Three
Thread-3 processing Four
Thread-2 processing Five
Exiting Thread-2
Exiting Thread-3
Exiting Thread-1
Exiting Main Thread

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/402216.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Tomcat8 连接池

1、所有的tomcat项目共用一个连接池配置 1.1 修改conf->context.xml文件&#xff0c;在Context节点下配置 <Resource name"jdbc/myDataSource" type"javax.sql.DataSource" driverClassName"com.microsoft.sqlserver.jdbc.SQLServerDriver"…

Linux 设备驱动中的 I/O模型(一)—— 阻塞和非阻塞I/O

在前面学习网络编程时&#xff0c;曾经学过I/O模型 Linux 系统应用编程——网络编程&#xff08;I/O模型&#xff09;&#xff0c;下面学习一下I/O模型在设备驱动中的应用。 回顾一下在Unix/Linux下共有五种I/O模型&#xff0c;分别是&#xff1a; a -- 阻塞I/O b -- 非阻塞I/O…

3.改变 HTML 内容

①xdocument.getElementById("demo") //查找元素 ②x.innerHTML"Hello JavaScript"; //改变内容 <!DOCTYPE html><html><body> <h1>我的第一段 JavaScript</h1> <p id"demo">JavaScript 能改变 HTML 元素的…

Python 爬虫进阶六之多进程的用法

python 中的多线程其实并不是真正的多线程&#xff0c;并不能做到充分利用多核 CPU 资源。 如果想要充分利用&#xff0c;在 python 中大部分情况需要使用多进程&#xff0c;那么这个包就叫做 multiprocessing。 借助它&#xff0c;可以轻松完成从单进程到并发执行的转换。mult…

DEFINE_PER_CPU

转自 http://www.unixresources.net/linux/clf/linuxK/archive/00/00/47/91/479165.html 首先&#xff0c;在arch/i386/kernel/vmlinux.lds中有 /*will be free after init*/ .ALIGN(4096); __init_begin.; /*省略*/ .ALIGN(32); __per_cpu_start.; .data.percpu:{*(.data.perc…

HDU 1213 How Many Tables(并查集模板)

http://acm.hdu.edu.cn/showproblem.php?pid1213 题意&#xff1a; 这个问题的一个重要规则是&#xff0c;如果我告诉你A知道B&#xff0c;B知道C&#xff0c;这意味着A&#xff0c;B&#xff0c;C知道对方&#xff0c;所以他们可以留在一个桌子。例如&#xff1a;如果我告诉你…

Linux 设备驱动的并发控制

Linux 设备驱动中必须要解决的一个问题是多个进程对共享的资源的并发访问&#xff0c;并发的访问会导致竞态&#xff0c;即使是经验丰富的驱动工程师也常常设计出包含并发问题bug 的驱动程序。 一、基础概念 1、Linux 并发相关基础概念 a -- 并发&#xff08;concurrency&#…

Python爬虫入门一综述

网络爬虫是一种自动抓取万维网信息的程序。 学习python爬虫&#xff0c;需要学习以下知识&#xff1a; python基础python中的urllib和urllib2库的用法python正则表达式python爬虫框架scrapypython爬虫高级功能 1.python基础 廖雪峰python教程 2.python urllib和urllib2库使…

Python爬虫学习二爬虫基础了解

1.什么是爬虫 爬虫就是进入网页自动获取数据的程序。当它进入一个网页时&#xff0c;将网页上需要的数据下载下来&#xff0c;并跟踪网页上的其他链接&#xff0c;进入新的页面下载数据&#xff0c;并继续跟踪链接下载数据。 2.URL URL&#xff0c;即统一资源定位符&#xf…

第三章:多坐标系

第一节&#xff1a;为什么要有多坐标系 当我们使用一个坐标系来描绘整个场景的时候&#xff0c;场景中的任意点都可以用该坐标系描述&#xff0c;此时如果有一只羊一遍摇动着耳朵&#xff0c;一边走&#xff0c;这个时候如果进行坐标的转换会发现异常的麻烦&#xff0c;此时如果…

Linux 设备驱动开发 —— 设备树在platform设备驱动中的使用

关与设备树的概念&#xff0c;我们在Exynos4412 内核移植&#xff08;六&#xff09;—— 设备树解析 里面已经学习过&#xff0c;下面看一下设备树在设备驱动开发中起到的作用 Device Tree是一种描述硬件的数据结构&#xff0c;设备树源(Device Tree Source)文件&#xff08;以…

Python爬虫入门三urllib库基本使用

urllib是一个收集了多个涉及了URL的模块的包&#xff1a; URL获取网页 urllibtest.pyimport urllib2 response urllib2.urlopen(http://www.baidu.com) print(response.read())运行结果&#xff1a; C:\Python27\python.exe H:/spiderexercise/spidertest/urllibtest.py &l…

使用老毛桃U盘重装Windows10系统

使用老毛桃U盘启动盘重装Windows10系统&#xff0c;这个方法很常用&#xff0c;相比较一些别的方法去重装系统&#xff0c;这个方法更方便更简单&#xff0c;容易入手和掌握。 在重装系统前&#xff0c;先去微软的官网把Windows10的ISO镜像文件下载下来&#xff0c;去别的网站下…

Android 网络通信框架Volley简介(Google IO 2013)

1. 什么是Volley 在这之前&#xff0c;我们在程序中需要和网络通信的时候&#xff0c;大体使用的东西莫过于AsyncTaskLoader&#xff0c;HttpURLConnection&#xff0c;AsyncTask&#xff0c;HTTPClient&#xff08;Apache&#xff09;等&#xff0c;今年的Google I/O 2013上&…

Linux 设备驱动开发 —— platform设备驱动应用实例解析

前面我们已经学习了platform设备的理论知识Linux 设备驱动开发 —— platform 设备驱动 &#xff0c;下面将通过一个实例来深入我们的学习。 一、platform 驱动的工作过程 platform模型驱动编程&#xff0c;需要实现platform_device(设备)与platform_driver&#xff08;驱动&am…

python使用proxy

使用urllib proxies {http: http://host:port} resp urllib.urlopen(url, proxiesproxies) content resp.read()使用urllib2 import urllib2enable_proxy True proxy_handler urllib2.ProxyHandler({"http" : your_proxy}) null_proxy_handler urllib2.Proxy…

There is no public key available for the following key IDs:3B4FE6ACC0B21F32

ubuntu 运行完sudo apt-get update之后&#xff0c;提示 W: There is no public key available for the following key IDs: 3B4FE6ACC0B21F32 解决方法是执行: sudo apt-key adv --recv-keys --keyserver keyserver.ubuntu.com 3B4FE6ACC0B21F32 参考 linux下apt-get出现“no …

Unichar, char, wchar_t

之前总结了一些关于字符表示&#xff0c;以及字符串的知识。 现在在看看一些关于编译器支持的知识。 L"" Prefix 几乎所有的编译器都支持L“” prefix&#xff0c;一个字符串如果带有L“”prefix&#xff0c;意味着这个字符串中的字符都被作为wide char存储&#xf…

Python爬虫入门四urllib库的高级用法

1.设置headers 有些网站不会同意程序直接用上面的方式进行访问&#xff0c;如果识别有问题&#xff0c;那么站点根本不会响应&#xff0c;所以为了完全模拟浏览器的工作&#xff0c;我们需要设置一些 Headers 的属性。 首先&#xff0c;打开我们的浏览器&#xff0c;调试浏览器…

进程上下文、中断上下文及原子上下文

谈论进程上下文 、中断上下文 、 原子上下文之前&#xff0c;有必要讨论下两个概念&#xff1a; a -- 上下文 上下文是从英文context翻译过来&#xff0c;指的是一种环境。相对于进程而言&#xff0c;就是进程执行时的环境&#xff1b; 具体来说就是各个变量和数据&#xff0c;…