爬虫 - 多线程

目录

1、线程的基础内容

2、线程与进程的区别

3、继承方式实现多线程

4、线程的常用方法

5、多线程访问全局变量的安全性问题

6、锁机制


1、线程的基础内容

  • 程序
    • Program,是一个指令的集合
    • 就是使用编程语言所编写指令集合,用于实现一定的功能
  • 进程
    • 启动后的程序称为进程,系统会有进程分配内存空间
    • 一个进程中至少包含一个线程
  • 线程
    • CPU的调度执行的基本单元
    • 一个进程中包含N多个线程
    • 进程结束,线程一定结束,线程结束,进程不一定结束
    • 同一个进程中的多个线程,共享内存地址

2、线程与进程的区别

区别

进程

线程

根本区别

资源分配的单位

调度和指定的单位

开销

每个进程都有独立的代码和数据空间(寄存器、堆栈、上下文)

线程是轻量级的进程,同一进程的线程共享代码和数据空间,线程间的切换开销小

所处环境

在操作系统中同时运行多个任务(程序)

同一应用程序中有多个顺序流同时执行

分配内存

系统在运行的时候会为每一个进程分配不同的内存空间

除了CPU之外,不会为线程分配内存(线程所使用的资源是它所属于的进程的资源),同一进程的线程共享资源

包含关系

进程中包含N多个线程,只有一个线程的进程称为单线程

线程是进程的一部分

import time
import threading

def fun1():
    for i in range(5):
        print('fun1中i的值为:', i)
        time.sleep(1)

def fun2():
    for i in range(5):
        print('fun2中i的值为:', i)
        time.sleep(1)

def single():
    fun1()      # 用了5秒
    fun2()      # 用了5秒,共用5+5=10秒

def mult():
    t1 = threading.Thread(target=fun1)  # 如果有(),那就是把函数的结果赋值给target
    t2 = threading.Thread(target=fun2)
    # 启动线程
    t1.start()     # t1,t2交叉运行,总共耗时5秒
    t2.start()     # 每次交叉的结果不一样,一会这个先,一会那个先运行

if __name__ == '__main__':
    # single()
    mult()

3、继承方式实现多线程

  • 为什么要使用类的方式创建线程
    • 因为类可以更加方便管理我们的代码,可以让我们使用面向对象的方式进行编程
  • 实现多线程的方式
    • 继承 threading.Thread类
    • 实现run()方法
    • 调用线程Thread类的start()方法启动线程

import time
import threading

class CodingThread(threading.Thread):
    def run(self):
        for i in range(5):
            print('fun1中i的值为:', i)
            time.sleep(1)

class CodingThread2(threading.Thread):
    def run(self):
        for i in range(5):
            print('fun2中i的值为:', i)
            time.sleep(1)

def mult():
    t1=CodingThread()
    t2=CodingThread2()
    t1.start()
    t2.start()

if __name__ == '__main__':
    mult()

4、线程的常用方法

序号

方法名称

描述

1

threading.current_thread()

获取当前线程对象

2

threading.enumerate()

获取当前运行的N条多线程信息

3

getName()

获取线程的名称

4

setName(name)

设置线程名称

import time
import threading

class CodingThread(threading.Thread):
    def run(self):
        thread=threading.current_thread()
        print(thread)
        print('线程的名称:',thread.getName())    # 显示手动创建的3个线程名称
        # 修改线程名称
        thread.setName('我的线程1')
        print('修改之后的线程名称:', thread.getName())
        for i in range(5):
            print('fun1中i的值为:', i)
            time.sleep(1)

class CodingThread2(threading.Thread):
    def run(self):
        thread = threading.current_thread()
        print(thread)
        print('线程的名称:', thread.getName())
        for i in range(5):
            print('fun2中i的值为:', i)
            time.sleep(1)

def mult():
    t1 = CodingThread()
    t2 = CodingThread2()
    t3 = CodingThread2()    # 3个进程同时运行
    t1.start()
    t2.start()
    t3.start()
    print(threading.enumerate())    # 当前有4条线程正在运行:主线程,123线程

if __name__ == '__main__':
    mult()

5、多线程访问全局变量的安全性问题

  • 多线程可以提高程序的运行效率,但同时也会有访问全局变量的安全性问题
  • 案例:车站售票

爬虫 - 多线程_第1张图片

以下代码输出结果可能会产生两个线程同时运行,导致后面部分没运行的结果

import time
import threading

ticket=100      # 全局变量
def sale_ticket():
    global ticket   # 在函数中对全局变量进行修改的时候,需要在变量前加global
    for i in range(100):
        if ticket>0:
            print(threading.current_thread().getName()+'-->正在出售第{}张票'.format(ticket))
            ticket-=1
        time.sleep(1)

def start():
    for i in range(2):
        t=threading.Thread(target=sale_ticket)
        t.start()

if __name__ == '__main__':
    start()     # 调用自定义的start()函数,创建线程对象,启动线程

6、锁机制

  • threading.Lock类
    • 为了解决多线程访问全局变量造成的安全性问题可以采用锁机制
    • 访问全局变量无需加锁
    • 修改全局变量时才需要加锁,修改完毕后释放锁
  • 加锁的操作步骤
    • 创建锁对象 threading.Lock()
    • 加锁 .acquire()
    • 释放锁 .release()

import time
import threading
ticket=100      # 全局变量
# 创建锁对象
lock=threading.Lock()
def sale_ticket():
    global ticket   # 在函数中对全局变量进行修改的时候,需要在变量前加global
    for i in range(100):
        lock.acquire()    # 加锁 (上锁操作)
        if ticket>0:
            print(threading.current_thread().getName()+'-->正在出售第{}张票'.format(ticket))
            ticket-=1
        time.sleep(1)
        lock.release()  # 开锁(释放锁操作),开锁了之后上面可以继续运行

def start():
    for i in range(2):
        t=threading.Thread(target=sale_ticket)
        t.start()

if __name__ == '__main__':
    start()     # 调用自定义的start()函数,创建线程对象,启动线程

你可能感兴趣的