Python_多线程与多进程编程_part2

上一节记录了多线程技术以及Python多线程的的简单上手.毫无疑问,多线程是为了充分利用硬件资源尤其是CPU资源来提高任务处理效率的技术。将任务拆分为多个线程同时运行,那么属于同一个任务的多个线程之间必然会有交互和同步以便互相协作完成任务。

3. 线程同步技术

使用线程同步技术有两个原因:

  • 数据安全问题,多个线程去取同一个数据源中的数据,如果不加同步锁会导致数据的脏读问题。
  • 协作顺序问题,多个线程完成同一个任务时,线程之间应该有同步和交互来协调各个线程。

Python的threading模块提供了多种用于线程同步的对象,在后面会一一介绍。

3.1 Lock/RLock对象

Lock是比较低级的同步原语,当被锁定以后不属于特定的线程。一个锁有两种状态:locked 和 unlocked。

  • 如果锁处于unlocked状态,acquire()方法将其修改为locked并立即返回
  • 如果锁已处于locked状态,则阻塞当前线程并等待其他线程释放锁,然在获取到了锁后将其修改为locked并立即返回。
    • release()方法用来将锁的状态由locked修改为unlocked并立即返回,如果锁状态本就是unlocked,调用该方法会抛出异常.

可重入锁RLock对象也是一种常用的线程同步原语,可以被同一个线程acquire()多次。

  • 当处于locked状态时,某线程拥有该锁
  • 当处于unlocked状态时,该锁不属于任何线程。

RLock对象的acquire()/release()调用时可以嵌套,仅当最后一个或最外层的release()执行结束,锁才会被设置为unlocked状态

使用Lock/RLock对象实现线程同步。
import threading
import time


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

    def run(self):
        global x
        lock.acquire() # 如果没有这一句,结果会重叠
        for i in range(3):
            x += i
        time.sleep(2)
        print(x)
        lock.release()


lock = threading.RLock()  # lock =threading.Lock()
t1 = []
for i in range(10):
    t = MyThread()
    t1.append(t)
x = 0
for i in t1:
    i.start()

3
6
9
12
15
18
21
24
27
30

3.2 Condition对象

使用Condition对象可以在某些事件触发后才处理数据,可以用于不同线程之间的通信或通知,以实现更高级的同步。Condition对象除了具有acquire()和release()方法之外,还有wait(),notify(),notify_all()等方法。下面通过经典生产者-消费者问题来演示Condition对象的用法。

使用Condition对象实现线程同步。
  • 首先实现生产者线程类:
class Producer(threading.Thread):
    """
    生产者线程类
    """

    def __init__(self, threadname):
        threading.Thread.__init__(self, name=threadname)

    def run(self):
        global x
        con.acquire()
        if x == 20:
            con.wait()  # 如果生产了20个产品,当前生产线程就会等待消费者消费
        else:
            print('\nProducer:', end='')
            for i in range(20):
                print(x, end=' ')
                x += 1
            print(x)
            con.notify()  # 生产结束,释放锁,提醒消费者进行消费
        con.release()
  • 接下来实现消费者线程类:
class Consumer(threading.Thread):
    """
    消费者线程类
    """

    def __init__(self, threadname):
        threading.Thread.__init__(self, name=threadname)

    def run(self):
        global x
        con.acquire()
        if x == 0:
            con.wait()  # 如果商品数量是0,当前消费者线程就会等待生产者线程生产产品后提醒自己
        else:
            print('\nConsumer:', end='')
            for i in range(20):
                print(x, end=' ')
                x -= 1
            print(x)
            con.notify()  # 消费完商品,释放锁,提醒生产者进程进行生产操作
        con.release()
  • 创建Condition对象以及生产者线程和消费者线程。
con = threading.Condition()
x = 0
p = Producer('Producer')
c = Consumer('Consumer')
p.start()
c.start()
p.join()
c.join()
print('After Producer and Consumer all done:', x)
Producer:0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

Consumer:20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
After Producer and Consumer all done: 0

3.3 queue对象

queue模块实现了多生产者-消费者队列,尤其适合需要在多个线程之间进行信息交换的场合,该模块的queue对象实现了多线程编程所需要的所有锁语义

import threading
import time
import queue


class Producer(threading.Thread):
    def __init__(self, threadname):
        threading.Thread.__init__(self, name=threadname)

    def run(self):
        global myqueue
        myqueue.put(self.getName())
        print(self.getName(), 'put', self.getName(), 'to queue.')


class Consumer(threading.Thread):
    def __init__(self, threadname):
        threading.Thread.__init__(self, name=threadname)

    def run(self):
        global myqueue
        print(self.getName(), 'get', myqueue.get(), 'from queue.')


myqueue = queue.Queue()
plist = [] # 生产者线程列表
clist = [] # 消费者线程列表

for i in range(10):
    p = Producer('Producer'+str(i))
    plist.append(p)
    c = Consumer('Consumer'+str(i))
    clist.append(c)

for i in plist:
    i.start()
    i.join()

for i in clist:
    i.start()
    i.join()

Producer0 put Producer0 to queue.
Producer1 put Producer1 to queue.
Producer2 put Producer2 to queue.
Producer3 put Producer3 to queue.
Producer4 put Producer4 to queue.
Producer5 put Producer5 to queue.
Producer6 put Producer6 to queue.
Producer7 put Producer7 to queue.
Producer8 put Producer8 to queue.
Producer9 put Producer9 to queue.
Consumer0 get Producer0 from queue.
Consumer1 get Producer1 from queue.
Consumer2 get Producer2 from queue.
Consumer3 get Producer3 from queue.
Consumer4 get Producer4 from queue.
Consumer5 get Producer5 from queue.
Consumer6 get Producer6 from queue.
Consumer7 get Producer7 from queue.
Consumer8 get Producer8 from queue.
Consumer9 get Producer9 from queue.

3.4 Event对象

Event对象是一种简单的线程通信技术,一个线程设置Event对象,另一个线程等待Event对象。

  • Event对象的set()方法可以设置Event对象内部的信号标志为真
  • clear()方法可以消除Event对象内部的信号标志,将其设置为假
  • isSet()方法用来判断其内部信号标志的状态
  • wait()方法只有在其内部信号状态为真时将很快地执行并返回,若内部信号状态为假,wait()方法将一直等待超时或内部信号状态为真。
使用Event对象实现线程同步
import threading


class MyThread(threading.Thread):
    def __init__(self, threadname):
        threading.Thread.__init__(self, name=threadname)

    def run(self):
        global myevent
        if myevent.isSet():  # 如果Event内部信号标志为真,就将其设置为假,并且设置该线程开始等待
            myevent.clear()
            myevent.wait()
            print(self.getName())
        else:  # 如果内部信号标志位假,就将其设置为真
            print(self.getName())
            myevent.set()


myevent = threading.Event()
myevent.set()
t1 = []
for i in range(10):
    t = MyThread('Thread'+str(i))
    t1.append(t)

for i in t1:
    i.start()

Thread1
Thread0
Thread3
Thread2
Thread5
Thread4
Thread7
Thread6
Thread9
Thread8

4. 多进程编程

Python标准库multiprocessing支持使用类似于threading的用法来创建与管理进程,并且避免了GIL(Global Interpreter Lock) 问题,可以更有效地利用CPU资源

4.1 创建进程

与使用threading创建和启动线程类似,可以通过创建Process对象来创建一个进程并通过调用进程对象的start()方法来启动。

from multiprocessing import Process
import os


def f(name):
    print('module name:', __name__)
    print('parent process id:', os.getppid())  # 查看父进程id
    print('this process id:', os.getpid())  # 查看当前进程ID
    print('hello',name)

if __name__ == '__main__':
    p = Process(target=f,args=('bob',))
    p.start()
    p.join()

multiprocessing 还提供了Pool对象支持数据的并行操作。例如,下面的代码可以并发计算二维数组每行的平均值

from multiprocessing import Pool
from statistics import mean


def f(x):
    return mean(x)


if __name__ == '__main__':
    x = [list(range(10)), list(range(20, 30)), list(range(50, 60)), list(range(80, 90))]
    with Pool(5) as p:  # 5个并行度
        print(p.map(f, x))

4.2 进程间数据交换

使用Queue对象在进程间交换数据
import multiprocessing as mp

def foo(q):
    q.put('hello world!')


if __name__ == '__main__':
    mp.set_start_method('spawn') # Windows系统创建子进程的默认方式spawn
    q = mp.Queue()
    p = mp.Process(target=foo,args=(q,))
    p.start()
    p.join()
    print(q.get())

也可以使用上下文对象context的Queue对象实现进程之间的数据交换

import multiprocessing as mp

def foo(q):
    q.put('hello world!')

if __name__ == '__main__':
    ctx = mp.get_context('spawn')
    q = ctx.Queue()
    p = ctx.Process(target=foo,args=(q,))
    p.start()
    p.join()
    print(q.get())

使用管道实现进程间数据交换

from multiprocessing import Process, Pipe


def f(conn):
    conn.send('hello world')  # 向管道中发送数据
    conn.close()


if __name__ == '__main__':
    parent_conn, child_conn = Pipe()  # 创建管道对象
    p = Process(target=f, args=(child_conn,))  # 将管道的一端作为参数传递给子进程
    p.start()
    print(parent_conn.recv())  # 通过管道的另一端获取数据
    p.join()

使用共享内存实现进程间的数据交换

from multiprocessing import Process, Value, Array


def f(n,a):
    n.value=3.1415926
    for i in range(len(a)):
        a[i] = a[i]*a[i]


if __name__ == '__main__':
    num = Value('d',0.0) # 实型
    arr = Array('i',range(10)) # 整型数组
    p = Process(target=f,args=(num,arr))
    p.start()
    p.join()
    print(num.value)
    print(arr[:])

使用Manager对象实现进程间数据交换

  • Manager对象控制一个拥有list,dict,Lock,RLock,Semaphore,BoundedSemaphore,Condition,Event,Barrier,Queue,Value,Array,Namespace等对象的服务端进程,并且运行其他进程访问这些对象
from multiprocessing import Process, Manager


def f(d, l, t):
    d['name'] = 'ChanZany'
    d['age'] = 24
    d['sex'] = 'Male'
    d['affiliation'] = 'CQUPT'
    l.reverse()
    t.value = 3


if __name__ == '__main__':
    with Manager() as manager:
        d = manager.dict()
        l = manager.list(range(10))
        t = manager.Value('i', 0)
        p = Process(target=f, args=(d, l, t))
        p.start()
        p.join()
        for item in d.items():
            print(item)
        print(l)
        print(t)
        print(t.value)

4.3 进程同步

在需要协同工作完成大型任务时,多个进程间的同步非常重要。进程同步方法与线程同步方法类似,代码稍微改写一些即可。

使用Lock对象实现进程同步
from multiprocessing import Process,Lock

def f(l,i):
    l.acquire() # 获取锁
    try:
        print('process--',i)
    except Exception as e:
        print(e)
    finally:
        l.release() #释放锁

if __name__ == '__main__':
    lock = Lock() # 创建锁对象
    for num in range(10):
        Process(target=f,args=(lock,num)).start()
使用Event对象实现进程同步
from multiprocessing import Process,Event

def f(e,i):
    if e.is_set():
        e.wait()
        print('process--',i)
        e.clear() # 相当于释放锁
    else:
        e.set() # 相当于获取锁

if __name__ == '__main__':
    e = Event()
    for num in range(10):
        Process(target=f,args=(e,num)).start()

小结

  • 线程数量并不是越多越好
  • threading模块是Python支持多线程编程的重要模块,提供了Thread,Lock,RLock,Condition,Event,Timer,Semaphore等大量类和对象
  • Thread类支持两种方法来创建线程
    • 为其构造函数传递一个可调用对象
      t1 = threading.Thread(target=func1,args=(0,100)) # 创建线程1并在线程的run方法内调用外部函数func1(0,100)
    • 继承Thread类,重写__init__()和run()方法
  • 创建了线程对象后,可以调用其start()方法来启动该线程,join()方法用来等待线程结束或超时,一篇总结比较到位的博客
  • 可以通过设置线程的daemon属性来决定主线程结束时是否需要等待子线程结束
    • daemon属性的值默认为False,即主线程结束时检查并等待子线程结束,如果需要修改daemon属性的值必须在调用start()方法之前进行修改
  • 除了threading模块中提供的线程同步对象,queue模块也实现了多生产者-多消费者队列,尤其适合需要在多个线程之间信息交换的场合
  • 派生自Thread类的自定义线程类首先也是一个普通类,同时还拥有线程类特有的run(),start(),join()等一系列方法。也就是说,可以在线程类中定义其他方法并且通过线程对象调用
  • Python标准库multiprocessing用来创建和管理进程,并且有效避免了GIL(Global Interpreter Lock)的问题,可以更加有效地利用CPU资源
  • 可以通过创建Process对象来创建一个进程并通过调用进程对象的start()方法来启动该进程的运行
  • 可以使用Queue对象,管道,共享内存,Manager对象在进程之间交换数据。
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值