python爬虫之多线程介绍(二)

1 线程间资源的竞争

1.1 线程间资源竞争产生的原因

'''
线程间的资源竞争产生原因:
1)了解线程间的通信:多线程共享全局变量
2)多线程操作:在对一个线程的文件读写过程时,另一个线程就在执行
3)⼀个线程写⼊,⼀个线程读取,没问题,如果两个线程都写⼊,就会出现资源间的竞争
'''
# 1. 多线程共享全局变量
# 在demo1--> t1.start()开始之后设置time.sleep(10)保证demo1先执行完,再执行demo2 ,不会导致全局变量num的争夺
import threading
import time

num = 0
def demo1(nums):
    global num
    for i in range(nums): # 执行nums次
        num += 1
    print(num)

def demo2(nums):
    for i in range(nums): # 执行nums次
        num += 1
    print(num)

def main():
    print(num)

if __name__ == '__main__':
    t1 = threading.Thread(target=demo1,args=(100000,))
    t2 = threading.Thread(target=demo1, args=(100000,))
    t1.start()
    time.sleep(10) # 保证demo1先执行
    t2.start()
    time.sleep(3)
    main()
# 结果为
# 100000
# 200000
# 300000
# 2.多线程操作:在对一个线程的文件读写过程时,另一个线程就在执行,但如果两个线程都写⼊,就会出现资源间的竞争
# 此处没有设置time.sleep()保证demo1先执行,这会导致demo1和demo2都在抢夺num这个全局变量,导致demo1、demo2轮换进行
import threading
import time

num = 0
def demo1(nums):
    global num
    for i in range(nums): # 执行nums次
        num += 1
    print(num)

def demo2(nums):
    for i in range(nums): # 执行nums次
        num += 1
    print(num)

def main():
    print(num)

if __name__ == '__main__':
    t1 = threading.Thread(target=demo1,args=(1000000,))
    t2 = threading.Thread(target=demo1, args=(1000000,))
    t1.start()
    t2.start()
    time.sleep(3)
    main()

# 结果为
# 126348
# 1178282
# 1294649

1.2 线程间资源竞争的解决

1.2.1 互斥锁

当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制某个线程要更改共享数据时,先将其锁定,此时资源的状态为**“锁定”,其他线程不能改变**,只到该线程释放资源,将资源的状态变成"非锁定",其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。
在这里插入图片描述

'''
解决线程间资源竞争 —— 将线程加一把锁,等他执行完,再执行另外一个线程
创建一个互斥锁
语法: lock1 = threading.Lock()  # Lock 只能上一把锁
       lock2 = threading.RLock() # 可以上多把锁,但是加锁和解锁的数量要一一对应
'''
import threading
import time

lock1 = threading.Lock()
lock2= threading.RLock()

num = 0
def demo1(nums):
    global num
    # 上锁
    lock1.acquire()
    for i in range(nums): # 执行nums次
        num += 1
    # 解锁
    lock1.release()
    print(num)

def demo2(nums):
    # 上锁
    lock2.acquire()
    lock2.acquire()
    for i in range(nums): # 执行nums次
        num += 1
    # 解锁
    lock2.release()
    lock2.release()
    print(num)


def main():
    print(num)

if __name__ == '__main__':
    t1 = threading.Thread(target=demo1,args=(1000000,))
    t2 = threading.Thread(target=demo1, args=(1000000,))
    t1.start()
    t2.start()
    time.sleep(3)
    main()

# 结果为
# 1000000
# 2000000
# 2000000

1.2.2 死锁

在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。

'''
死锁
'''
import threading
import time

class MyThread1(threading.Thread):
    def run(self):
        # 对mutexA上锁
        mutexA.acquire()

        # mutexA上锁后,延时1秒,等待另外那个线程 把mutexB上锁
        print(self.name+'----do1---up----')
        time.sleep(1)

        # 此时会堵塞,因为这个mutexB已经被另外的线程抢先上锁了
        mutexB.acquire()
        print(self.name+'----do1---down----')
        mutexB.release()

        # 对mutexA解锁
        mutexA.release()

class MyThread2(threading.Thread):
    def run(self):
        # 对mutexB上锁
        mutexB.acquire()

        # mutexB上锁后,延时1秒,等待另外那个线程 把mutexA上锁
        print(self.name+'----do2---up----')
        time.sleep(1)

        # 此时会堵塞,因为这个mutexA已经被另外的线程抢先上锁了
        mutexA.acquire()
        print(self.name+'----do2---down----')
        mutexA.release()

        # 对mutexB解锁
        mutexB.release()

mutexA = threading.Lock()
mutexB = threading.Lock()

if __name__ == '__main__':
    t1 = MyThread1()
    t2 = MyThread2()
    t1.start()
    t2.start()

2 线程锁

前面我们讲了用互斥锁解决线程间资源的竞争问题,现在让我们进一步了解线程锁

2.1 Semaphore

详细解释见博客:多线程并发之Semaphore(信号量)使用详解

'''
线程锁之信号Semaphore:
这种锁允许一定数量的线程同时更改数据,它不是互斥锁。比如地铁安检,排队人很多,工作人员只允许一定数量的人进入安检区,其它的人继续排队。
'''

import time
import threading

n=0
def demo(num, se):
    global n
    se.acquire()
    for i in range(num):
        n += 1
    print("run the thread: %s" % n)
    time.sleep(1)
    se.release()

# 设置允许5个线程同时运行
semaphore = threading.BoundedSemaphore(5)
for i in range(20):
    t = threading.Thread(target=demo, args=(10000,semaphore))
    t.start()

2.2 Condition

Condition称作条件锁,依然是通过acquire()/release()加锁解锁。

语法
con = threading.Condition()
con.acquire() 上锁
con.wait() 线程挂起
con.notify() 被挂起的线程被唤醒
con.release() 解锁

wait([timeout])方法将使线程进入Condition的等待池。等待通知(notify()方法),并释放锁。使用前线程必须已获得锁定,否则将抛出异常。
notify()方法将从等待池挑选一个线程并通知(notify()方法),收到通知的线程将自动调用acquire()尝试获得锁定(进入锁定池),其他线程仍然在等待池中。调用这个方法不会释放锁定。使用前线程必须已获得锁定,否则将抛出异常。

'''
线程锁之条件Condition
'''
import threading

def demo1():
    con.acquire() # 上锁
    print('第1步-打印1')
    con.notify()  # 唤醒demo2的con.wait(),执行demo2
    con.wait()  # 进入等待池,等待唤醒
    print('第3步-打印3')
    con.notify() # 唤醒demo2的con.wait(),执行demo2
    con.release()  # 解锁
def demo2():
    con.acquire() # 上锁
    con.wait()  # 进入等待池,所以先执行demo1
    print('第2步-打印2')
    con.notify() # 唤醒demo1的con.wait(),执行demo1
    con.wait() # 进入等待池,等待唤醒
    print('第4步-打印4')
    con.release()  # 解锁
if __name__ == '__main__':
    con = threading.Condition()
    t1 = threading.Thread(target=demo1)
    t2 = threading.Thread(target=demo2)
    t2.start()
    t1.start()

2.3 Event

全局定义了一个Flag,如果Flag的值为False,那么当程序执行wait()方法时就会阻塞,如果Flag值为True,线程不再阻塞。这种锁,类似交通红绿灯(默认是红灯),它属于在红灯的时候一次性阻挡所有线程,在绿灯的时候,一次性放行所有排队中的线程。
事件主要提供了四个方法set()、wait()、clear()和is_set()。

调用clear()方法会将事件的Flag设置为False。
调用set()方法会将Flag设置为True。
调用wait()方法将等待“红绿灯”信号。
is_set():判断当前是否"绿灯放行"状态

学习和参考:Python threading 多线程模块

'''
调用clear()方法会将事件的Flag设置为False。
调用set()方法会将事件的Flag设置为True。
调用wait()方法将等待“红绿灯”信号。
is_set():判断当前是否"绿灯放行"状态
'''
import threading
import time
event = threading.Event()

def lighter():
    green_time = 5  # 绿灯时间
    red_time = 5 # 红灯时间
    event.set()
    # 初始设为绿灯:
    while True:
        print('绿灯亮')
        time.sleep(green_time)
        event.clear()  # 将事件的Flag设置为False
        print('红灯亮')
        time.sleep(red_time)
        event.set() #  将事件的Flag设置为True

def run(name):
    while True:
        if event.is_set():  # 判断是否可以通行
            print("一辆[%s] 呼啸开过..." % name)
            time.sleep(1)
        else:
            print("一辆[%s]开来,看到红灯,无奈的停下了..." % name)
            event.wait() # 将等待“红绿灯”信号
            print("[%s] 看到绿灯亮了,瞬间飞起....." % name)

if __name__ == '__main__':
    light = threading.Thread(target=lighter)
    light.start()
    for name in ['奔驰', '宝马', '奥迪']:
        car = threading.Thread(target=run, args=(name,))
        car.start()

2.4 with

with也相当于线程锁,语法如下:

with some_lock:
# 执行任务…


'''
with
'''
import threading
import time

lock = threading.Lock()
num = 0
def demo1(nums):
    global num
    with lock: # with 上锁
        for i in range(nums): # 执行nums次
            num += 1
    print(num)

def demo2(nums):  # with 上锁
    with lock:
        for i in range(nums): # 执行nums次
            num += 1
    print(num)


def main():
    print(num)

if __name__ == '__main__':
    t1 = threading.Thread(target=demo1,args=(1000000,))
    t2 = threading.Thread(target=demo1, args=(1000000,))
    t1.start()
    t2.start()
    time.sleep(3)
    main()

3 Queue线程和线程池

3.1 Queue线程

在线程中,访问一些全局变量,加锁是一个经常的过程。如果你是想把一些数据存储到某个队列中,那么Python内置了-个线程安全的模块叫做queue模块。
Python中的queue模块中提供了同步的、线程安全的队列类,包括 FIFO (先进先出)队列Queue, LIFO (后入先出)队列ifoQueue。
这些队列都实现了锁原语(可以理解为原子操作,即要么不做,要么都做完),能够在多线程中直接使用。可以使用队列来实现线程间的同步。

1初始化Queue(maxsize): 创建一个先进先出的队列。
2 qsize():返回队列的大小。
3 empty():判断队列是否为空。
4 full():判断队列是否满了。
5 get():从队列中取最后一个数据。
6 put():将一个数据放到队列中。

'''
# Queue 就是对数据结构中的栈和队列这种数据结构的封装
# 初始化Queue(maxsize):创建一个先进先出的队列。
# qsize():返回队列的大小。
# empty():判断队列是否为空。
# full():判断队列是否满了。
# get():从队列中取最后一个数据。
# put():将一个数据放到队列中。
'''

from queue import Queue
q = Queue(3)  # 队列内部设置最多3个
print(q.empty()) # True 队列是空的
print(q.full()) # False 队列没满
q.put(1)  # 存入一个
q.put(2)  # 存入第二个
q.put(3)   # 存入第三个
print('----------------------------------')
print(q.empty()) # True 队列是空的  False
print(q.full()) #  队列满  True
# q.put(4,timeout=2) # 堵塞状态  2秒后报错,不设置timeout将一直处于堵塞状态
q.put_nowait(4) # queue.Full  等同于timeout

print(q.get())  # 取出最外面一个,即放入的第三个
print(q.get())  # 取出放入的第二个
print(q.get())  # 取出放入的第一个
print(q.get(timeout=2)) # queue.Empty  两秒后报错
print(q.get_nowait())  # 等同于timeout

3.2 线程池

定义:预先创建好一个数量较为优化的线程组,在需要的时候立刻能够使用,就形成了线程池。
线程池的整体构造需要自己精心设计,比如某个函数定义存在多少个线程,某个函数定义什么时候运行这个线程,某个函数定义去获取线程获取任务,某个线程设置线程守护(线程锁之类的)。
这里就需要应用到queue线程的知识,把线程类当做元素添加到队列内,从而实现线程池。
学习和参考:Python threading 多线程模块

'''
分析:
1.实例化一个MyThreadPool的对象,在其内部建立了一个最多包含5个元素的阻塞队列,并一次性将5个Thread类型添加进去。
2.循环20次,每次从pool中获取一个thread类,利用该类,传递参数,实例化线程对象。
3.在run()方法中,每当任务完成后,又为pool添加一个thread类,保持队列中始终有5个thread类。
4.一定要分清楚,代码里各个变量表示的内容。t表示的是一个线程类,也就是threading.Thread,而obj才是正真的线程对象。
'''
import queue
import time
import threading

class MyThreadPool:
    def __init__(self, maxsize=5):
        self.maxsize = maxsize
        self._pool = queue.Queue(maxsize)   # 使用queue队列,创建一个线程池
        for _ in range(maxsize):
            self._pool.put(threading.Thread)
    def get_thread(self):
        return self._pool.get()

    def add_thread(self):
        self._pool.put(threading.Thread)

def run(i, pool):
    print('执行任务', i)
    time.sleep(1)
    pool.add_thread()   # 执行完毕后,再向线程池中添加一个线程类

if __name__ == '__main__':
    pool = MyThreadPool(5)  # 设定线程池中最多只能有5个线程类
    for i in range(20):
        t = pool.get_thread()   # 每个t都是一个线程类
        obj = t(target=run, args=(i, pool)) # 这里的obj才是正真的线程对象
        obj.start()
    print("活动的子线程数: ", threading.active_count()-1)

4 线程同步

线程的同步就是A线程和B线程的交替对话,可以通过condition实现,这个模块只是为了巩固上面的知识

实现如下对话:
天猫精灵:小爱同学
小爱同学:在
天猫精灵:现在几点了?
小爱同学:你猜猜现在几点了

import threading

class XiaoAi(threading.Thread):
    def __init__(self,cond):
        # super() 可以用来获取当前类的父类
        super().__init__(name='小爱')
        self.cond = cond

    def run(self):
        # self.cond.acquire()
        with self.cond:  # 用with实现上锁和解锁
            self.cond.wait()
            print('{}: 在'.format(self.name))
            self.cond.notify()
            self.cond.wait()
            print('{}: 你猜猜几点了'.format(self.name))
            self.cond.notify()
        # self.cond.release()

class TianMao(threading.Thread):

    def __init__(self, cond):
        # super() 可以用来获取当前类的父类
        super().__init__(name='天猫')
        self.cond = cond

    def run(self):
        self.cond.acquire()  # 用cond.require 实现上锁
        print('{}: 小爱同学'.format(self.name))
        # print(1)
        self.cond.notify()
        # print(2)

        self.cond.wait()
        # print(3)
        print('{}: 现在几点了?'.format(self.name))
        self.cond.notify()
        self.cond.release()   # 用cond.require 实现解锁

if __name__ == '__main__':
    # 定义一把条件锁
    cond = threading.Condition()
    xiaoai = XiaoAi(cond)
    tianmao = TianMao(cond)

    xiaoai.start()
    tianmao.start()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值