python多任务编程

30 篇文章 8 订阅

 Python 多任务的实现有3种方式:

  • 多进程模式;
  • 多线程模式;
  • 多进程+多线程模式。

多线程编程

线程的状态:

  • New 创建
  • Runnable 就绪。等待调度
  • Running 运行
  • Blocked 阻塞。阻塞可能在 Wait Locked Sleeping
  • Dead 消亡

线程的类型:

  • 主线程
  • 子线程
  • 守护线程(后台线程)
  • 前台线程

1、线程的创建

Python中使用线程有两种方式:

1. 函数式(_thread模块)

函数式:调用 _thread 模块中的start_new_thread()函数来产生新线程。语法如下:

_thread.start_new_thread ( function, args[, kwargs] )
#参数说明:function - 线程要执行的函数。args - 传递给线程函数的参数,必须是个tuple类型。kwargs - 可选参数。

实例如下:

import _thread
import time

#为线程定义一个函数
def print_time(threadName,delay):
    count = 0
    while count < 5:
      time.sleep(delay)   #time.sleep(t) 推迟t秒调用线程的运行
      count += 1
      print("%s:%s",format( threadName, time.ctime(time.time())))  #time.ctime()返回当前时间的可读形式

#创建两个线程
try:
    _thread.start_new_thread(print_time,("thread1",2)) 
    _thread.start_new_thread(print_time,("thread2",4)) 
except:
    print("Error:无法启动线程")    

while 1 :
    pass  

执行输出结果如下:

2. 用类来包装线程对象(threading模块)

_thread 提供了低级别的、原始的线程以及一个简单的锁,它相比于 threading 模块的功能还是比较有限的。

threading 模块除了包含 _thread 模块中的所有方法外,还提供的其他方法:

  • threading.currentThread(): 返回当前的线程变量。
  • threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
  • threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

除了使用方法外,线程模块同样提供了Thread类来处理线程,Thread类提供了以下方法:

  • run(): 用以表示线程活动的方法。
  • start():启动线程活动。
  • join([time]): 等待至线程中止。这阻塞调用线程直至线程的join() 方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生。
  • isAlive(): 返回线程是否活动的。
  • getName(): 返回线程名。
  • setName(): 设置线程名。

通过直接从 threading.Thread 继承创建一个新的子类,并实例化后调用 start() 方法启动新线程,即它调用了线程的 run() 方法:

import threading
import time

#定义新线程子类
class myThread(threading.Thread):

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

    def run(self):
        print ("开始线程:" + self.name)
        print_time(self.name, self.delay, 5)
        print ("退出线程:" + self.name)

#线程函数
def print_time(threadName,delay,counter):
    while counter:
        time.sleep(delay)
        print("{}: {}".format(threadName, time.ctime(time.time())))
        counter -= 1

#创建新线程
thread1 = myThread(1,"Thread1",1)
thread2 = myThread(2,"Thread2",2)

# 开启新线程
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print ("退出主线程")

执行输出结果如下:

2 . 线程同步

使用 Thread 对象的 Lock 和 Rlock 可以实现简单的线程同步,这两个对象都有 acquire 方法和 release 方法,对于那些需要每次只允许一个线程操作的数据,可以将其操作放到 acquire 和 release 方法之间。

多线程的优势在于可以同时运行多个任务(并发)。threading 模块给我们提供了一个 Lock 功能,访问资源的线程需要获得锁才能访问。

lock = threading.Lock()

获取锁

lock.acquire()

释放锁:

lock.release()

Python 提供了可重入锁(RLock)。RLock 内部维护着一个 Lock 和一个 counter 变量,counter 记录了 acquire 的次数,从而使得资源可以被多次 require。直到一个线程所有的 acquire 都被 release,其他的线程才能获得资源。

r_lock = threading.RLock()

实例:

import threading
import time

class myThread (threading.Thread):
    def __init__(self, threadID, name, delay):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.delay = delay
    def run(self):
        print ("开启线程: " + self.name)
         # 获取锁,用于线程同步
        threadLock.acquire()
        print_time(self.name, self.delay, 3)
        # 释放锁,开启下一个线程
        threadLock.release()

def print_time(threadName, delay, counter):
    while counter:
        time.sleep(delay)
        print ("{}: {}".format(threadName, time.ctime(time.time())))
        counter -= 1

threadLock = threading.Lock()
# 创建新线程
threads = [myThread(i,"Thread-"+i,i)for i in range(1,3)]

# 开启新线程
for thread in threads:
    thread.start()

# 等待所有线程完成
for thread in threads:
    thread.join()
print ("退出主线程")
    

执行输出结果如下:

3. 线程优先级队列( Queue)(线程间通信方式)

从一个线程向另一个线程发送数据最安全的方式可能就是使用 queue 库中的队列了。创建一个被多个线程共享的 Queue 对象,这些线程通过使用 put() 和 get() 操作来向队列中添加或者删除元素。

Python 的 Queue 模块中提供了同步的、线程安全的队列类,包括FIFO(先入先出)队列Queue,LIFO(后入先出)队列LifoQueue,和优先级队列 PriorityQueue。

Queue 模块中的常用方法:

  • Queue.qsize() 返回队列的大小
  • Queue.empty() 如果队列为空,返回True,反之False
  • Queue.full() 如果队列满了,返回True,反之False
  • Queue.full 与 maxsize 大小对应
  • Queue.get([block[, timeout]])获取队列,timeout等待时间
  • Queue.get_nowait() 相当Queue.get(False)
  • Queue.put(item) 写入队列,timeout等待时间
  • Queue.put_nowait(item) 相当Queue.put(item, False)
  • Queue.task_done() 在完成一项工作之后,Queue.task_done()函数向任务已经完成的队列发送一个信号
  • Queue.join() 实际上意味着等到队列为空,再执行别的操作

实例:

import queue,time,threading

exitFlag = 0

#定义一个线程类
class myThread (threading.Thread):
    def __init__(self, threadID, name, q):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.q = q      #队列
    def run(self):
        print ("开启线程:" + self.name)
        process_data(self.name, self.q)    
        print ("退出线程:" + self.name)

# 获取数据函数
def process_data(threadName, q):
    while not exitFlag:
        queueLock.acquire()
        if not workQueue.empty():    #队列非空,取出,否则释放队列锁
            data = q.get()
            queueLock.release()
            print ("{} processing {}".format(threadName, data))
        else:
            queueLock.release()
        time.sleep(1)

threadList = ["Thread-1", "Thread-2", "Thread-3"]
nameList = ["One", "Two", "Three", "Four", "Five"]
queueLock = threading.Lock()
workQueue = queue.Queue(10)

# 填充队列
queueLock.acquire()
for word in nameList:
    workQueue.put(word)
queueLock.release()

#实例化新线程
threads = [myThread(threadID+1, tName, workQueue) for threadID,tName in enumerate(threadList)]

# 等待所有线程完成
for t in threads:
    t.start()
    t.join()


# 等待队列清空
while not workQueue.empty():
    pass

# 通知线程是时候退出
exitFlag = 1

for t in threads:
    t.join()       #join()方法,等待至线程中止

print ("退出主线程")

执行输出结果如下:

 

Python 还提供了 Event 对象用于线程间通信(实现同步),它是由线程设置的信号标志,如果信号标志为真,则其他线程等待直到信号解除。

Event 对象实现了简单的线程通信机制,它提供了设置信号,等待,清除信号等用于实现线程间的通信。

  • 设置信号:set() 方法设置 Event 对象内部的信号标志为真。Event 对象提供了 isSe() 方法来判断其内部信号标志的状态。当使用 event 对象的 set() 方法后,isSet() 方法返回真
  • 清除信号:使用 Event 对象的 clear() 方法可以清除 Event 对象内部的信号标志,即将其设为假,当使用 Event 的 clear 方法后,isSet() 方法返回假
  • 等待:wait 的方法只有在内部信号为真的时候才会很快的执行并完成返回。当 Event 对象的内部信号标志位假时,则 wait 方法一直等待到其为真时才返回。

实例:

#线程列表中的线程,信号为真则清除信号并等待,为假则输出线程名,并设置信号为真
import threading


#定义一个线程类
class mThread(threading.Thread):
    def __init__(self, threadname):
        threading.Thread.__init__(self, name=threadname)

    def run(self):
        
        # global使用全局Event对象   
        global event
        
        # 判断Event对象内部信号标志
        if event.isSet():    #真则清除
            event.clear()
            event.wait()
            print(self.getName())
        else:
            print(self.getName())
            #设置Event对象内部信号标志
            event.set()

# 生成Event对象
event = threading.Event()


t1 = []   # 线程列表
for i in range(10):
    t = mThread(str(i))
    # 生成线程列表
    t1.append(t)

# 运行线程
for i in t1:
    i.start()

4、Condition 条件变量

        Python 提供了 Condition 对象。使用 Condition 对象可以在某些事件触发或者达到特定的条件后才处理数据,Condition 除了具有 Lock 对象的 acquire 方法和 release 方法外,还提供了 wait 和 notify 方法。线程首先 acquire 一个条件变量锁。如果条件不足,则该线程 wait,如果满足就执行线程,甚至可以 notify(通知) 其他线程。其他处于 wait 状态的线程接到通知后会重新判断条件。

        其中条件变量可以看成不同的线程先后 acquire 获得锁,如果不满足条件,可以理解为被扔到一个( Lock 或 RLock )的 waiting 池。直达其他线程 notify 之后再重新判断条件。不断的重复这一过程,从而解决复杂的同步问题。该模式常用于生产者消费者模式,具体看看下面在线购物买家和卖家的示例:

import threading, time

#买家
class Consumer(threading.Thread):
    def __init__(self, cond, name):
        # 初始化
        super(Consumer,self).__init__()
        self.cond = cond
        self.name = name
    def run(self):     
        time.sleep(1)
        self.cond.acquire()
        print(self.name + ': 我这两件商品一起买,可以便宜点吗')
        self.cond.notify()
        self.cond.wait()
        print(self.name + ': 我已经提交订单了,你修改下价格')
        self.cond.notify()
        self.cond.wait()
        print(self.name + ': 收到,我支付成功了')
        self.cond.notify()
        self.cond.wait()
        print(self.name + ': 那我就等你发货了')
        self.cond.notify()
        self.cond.release()
#卖家
class Producer(threading.Thread):
    def __init__(self, cond, name):
        super(Producer,self).__init__()
        self.cond = cond
        self.name = name

    def run(self):
        self.cond.acquire()
        # 释放对锁的占用,同时线程挂起在这里,直到被 notify 并重新占有琐。
        self.cond.wait()
        print(self.name + ': 可以的,你提交订单吧')
        self.cond.notify()
        self.cond.wait()
        print(self.name + ': 好了,已经修改了')
        self.cond.notify()
        self.cond.wait()
        print(self.name + ': 嗯,收款成功,马上给你发货')
        self.cond.notify()
        self.cond.wait()
        print(self.name + ': 商品发货了')
        self.cond.release()
        
       
print("主线程开始")

cond = threading.Condition()
consumer = Consumer(cond, '买家rrh')
producer = Producer(cond, '卖家tk')
consumer.start()
producer.start()
consumer.join()
producer.join()

print("主线程结束")

执行输出结果如下:

 

5. 后台进程(守护线程

默认情况下,主线程退出之后,如果子线程没有 join,那么主线程结束后,子线程也依然会继续执行。如果希望主线程退出后,其子线程也退出而不再执行,则需要设置子线程为后台线程。Python 提供了 setDeamon 方法。

python中多线程编程时,经常会用到join()和setDaemon()方法,下面分别简单介绍下两种方式的概念及用法。

1.join()方法 

     主线程A中,创建了子线程B,并且在主线程A中调用了B.join()方法,那么主线程A会在调用的地方等待,直到子线程B完成操作后,才可以接着往下执行。也就是说那么在调用这个线程时可以使用被调用线程的join方法。

     用法:

join[timeout]

     里面的参数是可选的,代表线程运行的最大时间,即如果超过了这个时间,不管这个子线程有没有执行完毕都会被回收,然后主线程或函数都会被接着执行的。
 

2. setDaemon()方法

在启动线程前(就是必须在start()方法之前设置)设置thread.setDaemon(True),就是设置该线程为守护线程,表示该线程是不重要的,进程退出时不需要等待这个线程执行完成。
这样做的意义在于:避免子线程无限死循环,导致退不出程序。

程序运行中,执行一个主进程,如果主线程又创建了一个子线程,主线程和子线程就兵分两路,分别运行,那么当主线程完成想要退出时,会检验子线程是否完整。如果子线程未完成,则会等待子线程完成后再退出。但有时候我们需要的是,只要主线程完成了,不管子线程是否完成,都要和主线程一起退出,这时就可以用setDaemon方法了。
 

多进程

Python 提供了非常好用的多进程包 multiprocessing,只需要定义一个函数,Python 会完成其他所有事情。借助这个包,可以轻松完成从单进程到并发执行的转换。multiprocessing 支持子进程、通信和共享数据、执行不同形式的同步,提供了 Process、Queue、Pipe、Lock 等组件。

1. 类 Process

 

创建进程的类:

Process([group [, target [, name [, args [, kwargs]]]]])
  • target 表示调用对象
  • args 表示调用对象的参数元组
  • kwargs表示调用对象的字典
  • name为别名
  • group实质上不使用

实例:

import time,multiprocessing

def worker(interval,name):
    print(name + '【start】')
    time.sleep(interval)
    print(name + '【end】')

if __name__ ="__main__":
    p1,p2,p3 = multiprocessing.Process(target=worker, args=(2, '两点水1')), multiprocessing.Process(target=worker, args=(2, '两点水2')), multiprocessing.Process(target=worker, args=(2, '两点水3'))
    p1.start()
    p2.start()
    p3.start()

    print("The number of CPU is:" + str(multiprocessing.cpu_count()))

    for p in multiprocessing.active_children():
        print("child   p.name:" + p.name + "\tp.id" + str(p.pid))

    print("END!!!!!!!!!!!!!!!!!")

执行输出结果如下:

也可以自己把进程创建成一个类,当进程 p 调用 start() 时,自动调用 run() (重写),实例:

import multiprocessing
import time

class ClockProcess(multiprocessing.Process):
    def __init__(self, interval):
        multiprocessing.Process.__init__(self)
        self.interval = interval

    def run(self):
        n = 5
        while n > 0:
            print("当前时间: {0}".format(time.ctime()))
            time.sleep(self.interval)
            n -= 1


if __name__ == '__main__':
    p = ClockProcess(3)
    p.start()
    p.join()
    print("End")

执行输出结果如下:

如果在子进程中添加了 daemon 属性,那么当主进程结束的时候,子进程也会跟着结束,本例就会直接输出,就直接输出End了。

如果想让子进程执行完该怎么做呢?join 方法的主要作用是:阻塞当前进程,直到调用 join 方法的那个进程执行完,再继续执行当前进程。

 

2. Pool

如果需要很多的子进程,使用进程池的方法批量创建子进程。实例:

from multiprocessing import Pool
import os, time, random

#进程运行信息
def long_time_task(name):
    print('进程的名称:{0} ;进程的PID: {1} '.format(name, os.getpid()))
    start = time.time()
    time.sleep(random.random() * 3)
    end = time.time()
    print('进程 {0} 运行了 {1} 秒'.format(name, (end - start)))

if __name__ == '__main__':
    print('主进程的 PID:{0}'.format(os.getpid()))

    p = Pool(4)   #同时运行4个进程
    for i in range(6):
        p.apply_async(long_time_task, args=(i,))    #pply_async 是异步非阻塞的。即不用等待当前进程执行完毕,随时根据系统调度来进行进程切换。
    p.close()
    
    # 等待所有子进程结束后在关闭主进程
    p.join()
    print('【End】')

注意: Pool 对象调用 join() 方法会等待所有子进程执行完毕,调用 join()之前必须先调用 close() ,调用close() 之后就不能继续添加新的 Process 了。

执行输出结果如下:

3. 进程间通信

Process 之间肯定是需要通信的,操作系统提供了很多机制来实现进程间的通信。Python 的 multiprocessing 模块包装了底层的机制,提供了Queue、Pipes 等多种方式来交换数据。以 Queue 为例,在父进程中创建两个子进程,一个往 Queue 里写数据,一个从 Queue 里读数据,如下:

from multiprocessing import Process, Queue
import os, time, random


def write(q):
    # 写数据进程
    print('写进程的PID:{0}'.format(os.getpid()))
    for value in ['两点水', '三点水', '四点水']:
        print('写进 Queue 的值为:{0}'.format(value))
        q.put(value)
        time.sleep(random.random())


def read(q):
    # 读取数据进程
    print('读进程的PID:{0}'.format(os.getpid()))
    while not q.:
        value = q.get(True)
        print('从 Queue 读取的值为:{0}'.format(value))


if __name__ == '__main__':
    # 父进程创建 Queue,并传给各个子进程
    q = Queue()
    pw = Process(target=write, args=(q,))
    pr = Process(target=read, args=(q,))
    # 启动子进程 pw
    pw.start()
    # 启动子进程pr
    pr.start()
    # 等待pw结束:
    pw.join()
    # pr 进程里是死循环,无法等待其结束,只能强行终止
    pr.terminate()

执行输出结果如下:

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

薛定谔的猫96

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值