Python多线程

会有一股神秘感。 提交于 2019-12-21 23:51:57

一、线程概念

在程序运行时,操作系统会创建一个进程,并且会创建一个线程,这个线程就是主线程,主线程可以创建子线程。线程看上去同时运行,其实是按照并发执行的,走走停停,一直到所有形线程完成为止。线程像进程一样会有生命周期,如下所示:

将程序进行多线程编程,其性能会得到很大提升。python线程对CPU密集型性能提高不大,对I/O密集型性能提高很大。

二、多线程示例

import threading
import time

class myThread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        
    def run(self):
        out()     #加入执行的程序代码
        
def out():
    print_time1()
    print_time2()

def print_time1():
    print("Locka is acquired")
    print("Lockb is acwuired")
    
def print_time2():
    print("Lockb is acquired")
    time.sleep(2)
    print("Locka is acwuired")

def main():
    for i in range(50):
        t = myThread()
        t.start()        #执行线程

main()

以上就是线程的简单程序,我们创建了50个线程,将他们同时运行。他们完成的时间不一样,其先后顺序也不能确定。使用方法就是自己写一个类,继承threading.Thread类,并重写方法run(),将自己要运行的程序放入run()函数之中就行。
但是,上述程序有一个问题,就是在调用函数out()时,可能在一个线程还没有执行完时,就暂停,CPU转而去执行另一个线程,导致另一个线程修改了这个线程的数据,导致输出错误的结果。解决办法就是在同一时刻就只能有一个线程访问临界资源,其他线程只能等待。

三、线程同步

在python中实现线程同步有多种方法

1. 线程锁(Lock)

GIL(全局解释器锁)

GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念,是为了实现不同线程对共享资源访问的互斥,才引入了GIL。以下是原理图:

我们对临界资源加上锁,这样其他线程就无法访问,直到这个线程完成操作,释放线程锁之后为止。如下代码:

import threading
import time
#创建锁
locka = threading.Lock() 
lockb = threading.Lock() 

class myThread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        
    def run(self):
        out()

def out():
    print_time1()
    print_time2()

def print_time1():
    locka.acquire()  #获取锁
    print("Locka is acquired")
    lockb.acquire()  #获取锁
    print("Lockb is acwuired")
    lockb.release()  #释放锁
    locka.release()  #释放锁

def print_time2():
    lockb.acquire()
    print("Lockb is acquired")
    time.sleep(2)
    locka.acquire()
    print("Locka is acwuired")
    locka.release()
    lockb.release()

def main():
    for i in range(50):
        t = myThread()
        t.start()

main()

在上面程序中,我们创建了两个锁locka和lockb,分别对临界资源加锁,这样就可以让同一时刻就只有一个线程执行,避免输出错误结果。但是上述代码还有一个错误,当第一个线程执行到函数print_time2()的time_sleep(2)时,需要获取锁locka,但是locka已经被第二个线程获取,还没有释放,而且第二个线程也需要获取lockb才能继续运行,但是lockb已被第一个线程获取,还没有释放,就这样,两个线程会一直等待,陷入死锁。解决办法是引入可重入锁。

2.递归锁(RLock)

递归锁就是在同一个线程中可以获取锁所多次,不会陷入死锁。但是在acquire()n次之后,需要release()n次。

import threading
import time

rlock = threading.RLock()   #RLock本身有一个计数器,如果碰到acquire,那么计数器+1
                            #如果计数器大于0,那么其他线程无法查收,如果碰到release,计数器-1

class myThread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        
    def run(self):
        out()

def out():
    print_time1()
    print_time2()

def print_time1():
    rlock.acquire()  #获取锁
    print("Locka is acquired")
    rlock.acquire()  #获取锁
    print("Lockb is acwuired")
    rlock.release()  #释放锁
    rlock.release()  #释放锁

def print_time2():
    rlock.acquire()
    print("Lockb is acquired")
    time.sleep(2)
    rlock.acquire()
    print("Locka is acwuired")
    rlock.release()
    rlock.release()

def main():
    for i in range(50):
        t = myThread()
        t.start()

main()

三、Semaphore(信号量)

threading模块里的Semaphore类实现了信号量对象,可用于控制获取资源的线程数量。所具有的acquire()和release()方法,可以用with语句的上下文管理器。当进入时,将调用acquire()方法,当退出时,将调用release()。

import threading
import time

sem = threading.Semaphore(3)   #设置线程并发数

class myThread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        
    def run(self):
        out()

def out():
    print_time1()
    print_time2()

def print_time1():
    sem.acquire()    #线程数减一
    print("Locka is acquired")
    print("Lockb is acwuired")
    sem.release()    #线程数加一

def print_time2():
    sem.acquire()   #线程数减一
    print("Lockb is acquired")
    print("Locka is acwuired")
    sem.release()   #线程数加一

def main():
    for i in range(10):
        t = myThread()
        t.start()

main()

四、Condition(条件变量)

Condition(条件变量)通常与一个锁关联。需要在多个Contidion中共享一个锁时,可以传递一个Lock/RLock实例给构造方法,否则它将默认生成一个RLock实例。

可以认为,除了Lock带有的锁定池外,Condition还包含一个等待池,池中的线程处于状态图中的等待阻塞状态,直到另一个线程调用notify()/notifyAll()通知;得到通知后线程进入锁定池等待锁定。

Condition():

  • acquire(): 线程锁
  • release(): 释放锁
  • wait(timeout): 线程挂起,并释放锁,直到收到一个notify通知或者超时(可选的,浮点数,单位是秒s)才会被唤醒继续运行。wait()必须在已获得Lock前提下才能调用,否则会触发RuntimeError。
  • notify(n=1): 调用这个方法将从等待池挑选一个线程并通知,收到通知的线程将自动调用acquire()尝试获得锁定(进入锁定池);其他线程仍然在等待池中。调用这个方法不会释放锁定。使用前线程必须已获得锁定,否则将抛出异常。 最多唤醒n个等待的线程。
  • notifyAll(): 调用这个方法将通知等待池中所有的线程,这些线程都将进入锁定池尝试获得锁定。调用这个方法不会释放锁定。使用前线程必须已获得锁定,否则将抛出异常。

以下就以生产者消费者为例:

import threading
import time
import random

con = threading.Condition()

class Goods():
    def __init__(self):
        self.__goods = 0
    
    def getgoods(self):
        return self.__goods

    def add(self):
        self.__goods += 1
    
    def sub(self):
        self.__goods -= 1

    def isEmpty(self):
        if self.__goods <= 0:
            return True
        else:
            return False

    def isFull(self):
        if self.__goods >= 10:
            return True
        else:
            return False

class Producer(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)

    def run(self):
        while True:
            con.acquire()           #获取锁
            while goods.isFull():   #货物满了,需要消费才能生产,进入阻塞
                con.wait()
            goods.add()             #生产一件货物
            print("生产一件货物,总货物数量为:", goods.getgoods())
            con.notifyAll()         #生产一件货物后便唤醒所有正在等待的消费者
            con.release()           #释放锁
            time.sleep(random.random())

class Consumer(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)

    def run(self):
        while True:
            con.acquire()           #获取锁
            while goods.isEmpty():  #货物消费完了,需要生产货物,进入阻塞
                con.wait()
            goods.sub()             #消费一件货物
            print("消费一件货物,总货物数量为:", goods.getgoods())
            con.notifyAll()         #消费一件货物后便唤醒所有正在等待的生产者
            con.release()           #释放锁
            time.sleep(random.random())

goods = Goods()

def main():
    threads = []
    #threads.append(Producer())
    #threads.append(Comsumer())

    for i in range(5):
        threads.append(Producer())
    
    for i in range(5):
        threads.append(Consumer())
    
    for th in threads:
        th.start()

main()

五、同步队列

让我们考虑更复杂的一种场景:产品是各不相同的。这时只记录一个数量就不够了,还需要记录每个产品的细节。很容易想到需要用一个容器将这些产品记录下来。

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

用FIFO队列实现上述生产者与消费者问题的代码如下:

import threading
import time
import random
import queue

q = queue.Queue()    #创建一个线程同步队列

class Producer(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)

    def run(self):
        while True:
            item = random.randint(0, 16)
            while q.qsize() >= 10:
                pass
            q.put(item)    #添加货物
            print("生产货物%02d, 队列大小:%02d" % (item, q.qsize()))
            time.sleep(random.randint(0, 3))

class Consumer(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)

    def run(self):
        while True:
            item = q.get()   #消费货物,若为空,会阻塞
            print("消费货物%02d, 队列大小:%02d" % (item, q.qsize()))
            time.sleep(random.randint(0, 3))

def main():
    threads = []
    
    for i in range(5):
        threads.append(Producer())
    
    for i in range(5):
        threads.append(Consumer())
    
    for th in threads:
        th.start()

main()

六、Event(事件)

python线程的事件用于主线程控制其他线程的执行,事件主要提供了三个方法wait、clear、set。

事件处理的机制:全局定义了一个“Flag”,如果“Flag”值为 False,那么当程序执行 event.wait 方法时就会阻塞,如果“Flag”值为True,那么event.wait 方法时便不再阻塞。

  • event.set() 设置标志位为True
  • event.clear() 清空标志位,标志位为False
  • event.wait() 等待设置标志位,阻塞
  • event.isSet() 判断标志位是True还是False

下面就采用红绿灯车通行的例子来示例:

import threading
import time

event = threading.Event()   


def Lighter():     
    event.set()
    count = 0
    while True: 
        if count > 5 and count <= 10: #红灯
            event.clear() #清除标志位
        elif count > 10:  #变为绿灯
            event.set()  #重新设置标志位
            count = 0
        time.sleep(1) 
        count += 1

def Car(name):
    while True:
        if event.isSet():  #判断标志位为True
            print("light is green, %d is running" % name)
            time.sleep(2)
        else:
            print("light is red, %d is waiting" % name)
            event.wait()   #阻塞,停车


def main():
    light = threading.Thread(target=Lighter) 
    light.start() 
    
    threads = []
    for i in range(5):    #开五部车
        threads.append(threading.Thread(target=Car, args=(i,)))
        
    for car in threads:
        car.start()


main()

这个程序将红绿灯的情况来控制车的通行,即用红绿灯这线程来控制车线程,达到一个线程控制多个线程的目的。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!