多线程入门

1多线程介绍

  • 先说多进程,在说多线程

1.1:什么是多进程

  • 系统正在运行的一个应用程序,一个应用程序等于一个进程。也就是说在系统中运行的应用程序占用了电脑cpu的一个进程,多进程的意思就是一个电脑中可以同时打开多个应用程序,现在的电脑基本都可以实现多进程。

1.2:多进程的特点

  • 一个cpu核心只能执行一个进程,其它进程就处于阻塞状态。多个cpu核心,就可以同时执行多个任务。

1.3:多线程

  • 进程中包含的执行单元

1.3.1:多线程的特点

  • 一个进程可以包含多个线程
  • 在python中一次只能进行一个线程,也就是说在python中的多线程不是真正的多线程
  • 线程锁(资源竞争问题),由于在python中一次只能执行一个线程,所以才需要使用线程锁

1.4:爬虫使用多线程的好处

  • 在一些场景下比较适合多线程,在一些的环境下可以优化程序,会在一个线程阻塞的时候,另一个线程快速的来连接

2:如何创建多线程

  • 1:通过函数创建
  • 2:通过类创建

2.1:通过函数创建多线程

  • 需要使用threading模块当中的一个Threa类,有一个参数target参数,需要我们传递一个函数对象。一般这个参数,是用来传递子线程的逻辑,target这个参数要使用关键字传参,传入的对象是一个函数对象(注意带括号的是一个函数的引用,不带括号的是一个函数的对象)
  • 代码:
# 导入模块
import threading  # 用来创建多线程的一个类
import time  # 包含时间的模块


# 创建多线程的方法
# 1:先创建一个子线程函数
def num():
    print('这是一个子线程')
    time.sleep(1)


if __name__ == '__main__':
    # 2:创建一个子线程,注意需要传递的是一个函数对象而不是一个函数的引用
    for i in range(5):
        t = threading.Thread(target=num)
        t.start()  # 创建并执行字线程
  • threading.Thread类的参数
    • target:需要传递一个函数对象,注意不是引用
    • name:线程的名字,方便分辨线程的名字和功能
    • agrs:以元组的形式传递给子线程参数

2.2:通过类来创建多线程

  • 需要自定义一个类,这个类继承threading.Thread方法
  • 在这个类中需要重写run方法
  • 在run方法中定义子线程
  • 代码:
# 导包
import threading
import time


# 自定义一个类
class T(threading.Thread):  # 继承父类的一个类

    def run(self) -> None:  # 重写run方法
        # 创建一个子线程
        for i in range(5):
            print('这是一个子线程')
            time.sleep(1)


if __name__ == '__main__':
    t = T()  # 实例化类
    t.start()  # 创建并执行线程

2.3:主线程和子线程的优先级

  • 在python中程序会等待子线程结束完的时候在结束,主线程不会等待子程序结束在执行
  • 可以使用threading.Thread中的一个方法join方法来使子程序执行完在执行主线程
  • 也可以使用setDaemon(True)来守护线程,使线程只执行一个线程

3:多线程的一些方法

3.1:查看多线程的数量

  • 需要使用一个函数:enumerate()
  • 代码:
def demo1():
    for i in range(5):
        print('demo1--%d' % i)


def main():
    print(threading.enumerate())
    t1 = threading.Thread(target=demo1, name='demo1')
    print(threading.enumerate())
    t1.start()  # 创建并执行线程
    print(threading.enumerate())


if __name__ == '__main__':
    main()

注意:

  • start(),是用来创建并执行线程
  • threading.Thread(…),是用来传递线程的时间函数的逻辑,并不创建线程

3.2:线程间的资源竞争

  • global:通过函数的内部来修改全局变量
  • 多线程的特点
    • 数据不完整
    • 报bug
  • 解决方法
    • 控制爬取的频率
  • 多线程有资源竞争问题,
    • 解决方法
      • 1:先把第一个线程的工作做完,在把第二个线程的工作做完
      • 2:时间等待,join方法

4:线程锁

  • 会使代码的效率降低
  • 作用:一个线程的数据要修改全局数据的时候就需要使用线程锁,默认情况下是不上锁
  • 副作用:会造成代码的效率下降

4.1:上锁代码

  • 先创建一个锁的实例
t = threading.Lock()
  • 上锁代码
lock.acquire()		# 上锁一定要在改变全局变量之前上锁
  • 解锁代码
lock.release()		# 同理,解锁也要在改变全局变量之前之后在解锁

注意

  • 多上一把锁会阻塞线程,这个时候需要把这个锁给解开,但是若上的锁的的数量多的时候会造成线程的阻塞,这个时候如果解锁也会造成线程的阻塞,这是因为lock为不可重复的锁,只能上一次锁和解一次锁,多上的话就会阻塞线程
  • 上多把锁的代码,需要使用Rlock这个代码来上锁
lock = threading.RLock()	  # 创建一个可以重复的锁的实例对象 

4.2:死锁

  • 在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。
  • 解决方法
    • 程序设计时要尽量避免
      添加超时时间等

5:线程队列

  • 线程队列的特点是先进先出
  • 线程队列是一个内置的模块,内部也实现了一些锁的一些特征
  • 线程模块:Queue

5.1:线程队列模块常用的方法

  • empty():判队列是否为空,队列为空返回True,队列不是空的时候返回False代表队列不是空的
  • full():判断队列是否是满的,当队列为满的的时候返回True,队列不是满的时候返回False
  • get():从队列中取出数据
  • put():向队列中添加数据
  • join():等待队列为空在进行别的操作
  • qsize():返回队列的长度

5.2:队列的长度

  • 在创建队列的实例化对象的时候,是可以指定队列的长度的,如果不指定队列的长度则会指定默认的长度,默认长度的队列大概可以添加2G的数据,一般情况下,我们在使用队列的时候都会指定一下这个队列的长度
  • 如果在列表中添加的数据超过了列表的长度就会出现阻塞的情况,在Python中的阻塞情况是最令人无奈的,代码在一直运行,但是不会出现报错
  • 出现阻塞的解决方法
    • 1:时间等待方法,在添加元素的时候,使用关键字参数(timeout=n)来使代码可以执行n秒,如果出现阻塞了就会报错
    • 2:非阻塞方法,使用另一个添加队列数据的方法:put_nowait(),这个添加队列数据的方法,只要队列出现阻塞的情况就会立马报错
  • 当取数据的个数超出列表中数据的个数,代码也会阻塞,解决方法和添加数据出现阻塞的处理情况一样

5.3:队列的使用场景

  • 在爬虫中,可以使用队列来存放要爬取的url地址,然后一个一个的取出数据,可以代替循环的便利来使用,可以说是一个新的思路
  • 注意:在创建线程的时候传递的参数是函数对象,而不是函数的调用
  • 如果在使用多线程的时候报出:jsondeccdeerror的错误,是因为抓取的频率太快了,json数据没有及时的返回,造成数据抓取不全
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值