继续讲多任务,上次咱们讲到了模拟多任务。这次接着讲创建线程。

一、多任务线程拓展二、线程同步的简单实现

一、多任务线程拓展

1.创建多任务线程的另一种方法

根据上一章我们知道,当调⽤Thread的时候,不会创建线程当调用Thread创建出来的实例对象的start方法的时候,才会创建线程以及开始运行这个线程

我们也可以通过继承Thread类创建线程

先创建一个类,其中super()是动态拿到父类的初始化属性,继承于父类。再用run方法来实现。

import threading
import time

class A(threading.Thread):
    
    def __init__(self,name):
        super().__init__(name=name)
    
    def run(self):
        for i in range(5):
            print(i)

if __name__ == "__main__":
    t = A('test_name')    
    t.start()

2.线程间的通信(多线程共享全局变量)

在⼀个函数中,对全局变量进⾏修改的时候,要在变量前加上global。是否要加,看是否对全局变量的指向进⾏了修改,如果修改了指向,那么必须使用global,此时是共享全局变量;仅仅是修改了指向的空间中的数据,此时**不是必须使用global。

创建两个子线程和一个主线程:

import threading
import time

num = 100

def demo1():

    global num

    num += 1

    print('demo1-num%d'%num)

def demo2():

    print('demo2-num%d' % num)

def main():

    t1 = threading.Thread(target=demo1)

    t2 = threading.Thread(target=demo2)

    t1.start()
    time.sleep(1)
    t2.start()

    print('main-num%d'%num)

if __name__ == '__main__':

    main()

运行结果如下:

python多线程爬虫 go协程爬虫 爬虫多线程模块_Python


可以看到,第一个子线程的结果传递到了第二个子线程和主线程,也就是实现了线程间的通信

另外,可以向线程中传递参数。

threading.Thread(target=test, args=(num,))

3.线程间的资源竞争及其解决方式

当多个线程同时需要相同的变量写入数据时 ,会产生资源竞争的问题。

就像下面这样:

import time
import threading

num = 0

def demo1(nums):
    global num

    for i in range(nums):
        num += 1
        
    print('demo1-num %d'%num)

def demo2(nums):
    global num

    for i in range(nums):
        num += 1
    
    print('demo2-num %d' % num)

def main():

    t1 = threading.Thread(target=demo1,args=(1000000,))
    t2 = threading.Thread(target=demo2,args=(1000000,))

    t1.start()
    t2.start()
    time.sleep(3)
    
    print('main-num %d'%num)

if __name__ == '__main__':

    main()

我们想要实现每个线程用全局变量将数字相加100万次(想得到的结果是1000000、2000000、2000000),但却得到了下面这样的结果(出现了资源竞争问题):

python多线程爬虫 go协程爬虫 爬虫多线程模块_多任务_02


我们应该怎么解决呢?这个时候就要使用一把锁把线程锁住。

3.1 锁的概念及使用

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

# 创建锁
mutex = threading.Lock()

# 锁定
mutex.acquire()

# 解锁
mutex.release()

咱们先来简单使用一下,在对涉及线程共享变量改变的部分上锁和解锁即可(这里仅对两个子线程修改即可):

mutex = threading.Lock()     # 在前面先创建锁

def demo1(nums):
    global num
    mutex.acquire()          # 上锁
    for i in range(nums):
        num += 1
    mutex.release()          # 解锁

    print('demo1-num %d' % num)

def demo2(nums):
    global num
    mutex.acquire()          # 上锁
    for i in range(nums):
        num += 1
    mutex.release()          # 解锁

    print('demo2-num %d' % num)

这次达到了我们想要的结果:

python多线程爬虫 go协程爬虫 爬虫多线程模块_多线程_03


以上这样创建的锁叫做不可重复的锁,只能创建一次。除此之外,还有可重复锁,可创建多次且不会出现死锁状态。

import time
import threading

num = 0

mutex = threading.RLock()    # 可重复锁创建
def demo1(nums):
    global num
    # 加锁(可以看到这里上了两把锁,此时如果继续用lock就会出现死锁的状态)
    mutex.acquire()          
    mutex.acquire()
    for i in range(nums):

        num += 1
    # 解锁
    mutex.release()
    mutex.release()

    print('demo1-num %d'%num)

def demo2(nums):
    global num
    mutex.acquire()
    for i in range(nums):
        num += 1
    mutex.release()

    print('demo2-num %d' % num)


def main():

    t1 = threading.Thread(target=demo1,args=(1000000,))
    t2 = threading.Thread(target=demo2,args=(1000000,))

    t1.start()
    t2.start()
    time.sleep(3)

    print('main-num %d'%num)

if __name__ == '__main__':

    main()

二、线程同步的简单实现

比如我们想要实现这样的一个逻辑:

天猫精灵:小爱同学
小爱同学:在
天猫精灵:现在几点了?
小爱同学:你猜猜现在几点了

一问一答的形式,用下面的代码实现。

先导入thread模块

import threading

再创建两个类,用继承的方式来实现线程:

# 第一个类
class XiaoAi(threading.Thread):

    def __init__(self,cond):

        super().__init__(name='小爱')

    def run(self):

        print('{}: 在'.format(self.name))

        print('{}: 你猜猜现在几点了'.format(self.name))
        
# 第二个类
class TianMao(threading.Thread):

    def __init__(self, cond):
    
        super().__init__(name='天猫')

    def run(self):

        print('{}: 小爱同学'.format(self.name))

        print('{}: 现在几点了?'.format(self.name))

现在就要加上锁来控制线程的先后发生,这次用一个新的锁Condition

# 第一个类
class XiaoAi(threading.Thread):

    def __init__(self,cond):

        super().__init__(name='小爱')
		self.cond = cond      # 创建Condition锁
		
    def run(self):
    
        self.cond.acquire()   # 此处上锁

        print('{}: 在'.format(self.name))
		self.cond.notify()

        self.cond.wait()
        print('{}: 你猜猜现在几点了'.format(self.name))
        self.cond.release()
        
# 第二个类
class TianMao(threading.Thread):

    def __init__(self, cond):
    
        super().__init__(name='天猫')
        self.cond = cond      # 创建Condition锁

    def run(self):
    
		self.cond.acquire()   # 此处上锁

        print('{}: 小爱同学'.format(self.name))
		self.cond.notify()    

        self.cond.wait()
        print('{}: 现在几点了?'.format(self.name))
        self.cond.release()

if __name__ == '__main__':

    # mutex = threading.RLock()
    cond = threading.Condition()
    xiaoai = XiaoAi(cond)
    tianmao = TianMao(cond)

    xiaoai.start()
    tianmao.start()

其中通过线程间的唤醒、等待操作来实现这样的一个逻辑。

今天的多任务模块就先讨论到这里,下一章咱们接着唠。