PYTHON基础知识学习笔记(十二)

并发编程

概述

1、非并发编程
程序由单个步骤序列构成,包含独立子任务的程序执行性能低。
2、并发编程
异步、高效,分解子任务、简化流程与逻辑。
3、进程 process
一个程序的执行实例,每个进程有自己的地址空间、内存、数据栈及辅助数据。
4、线程 thread
同一进程内可被并行激活的控制流,共享相同上下文(空间地址、数据结构)。
特点:
(1)便于信息共享和通讯;
(2)线程访问顺序差异会导致结果不一致。
5、python GIL 全局解释器锁
python代码由虚拟机(解释器主循环)控制,主循环同时只能有一个控制线程执行。

多线程

_thread模块

python内置模块,提供了基本的线程和互斥锁支持,目前已经不建议使用,这里利用该模块理解多线程与单线程的区别。
特点
1、没有控制进程结束机制;
2、只有一个控制原语(锁);
3、功能少于threading模块。
单线程实例
执行两次worker()函数,第一次耗时4s,第二次耗时2s,总耗时6s。

import time

def worker(n):
    print(f'函数执行开始于:{time.ctime()}')
    time.sleep(n)
    print(f'函数执行结束于:{time.ctime()}')

def main():
    print(f'【主函数执行开始于:{time.ctime()}】')
    worker(4)
    worker(2)
    print(f'【主函数执行结束于:{time.ctime()}】')

if __name__ == '__main__':
    main()

#运行结果
【主函数执行开始于:Fri Feb 21 16:08:20 2020】
函数执行开始于:Fri Feb 21 16:08:20 2020
函数执行结束于:Fri Feb 21 16:08:24 2020
函数执行开始于:Fri Feb 21 16:08:24 2020
函数执行结束于:Fri Feb 21 16:08:26 2020
【主函数执行结束于:Fri Feb 21 16:08:26 2020

多线程实例
同样执行两次worker()函数,第一次耗时4s,第二次耗时2s,总耗时小于6s。

import time
import _thread

def worker(n):
    print(f'函数执行开始于:{time.ctime()}')
    time.sleep(n)
    print(f'函数执行结束于:{time.ctime()}')

def main():
    print(f'【主函数执行开始于:{time.ctime()}】')
    _thread.start_new_thread(worker, (4,))
    _thread.start_new_thread(worker, (2,))
    time.sleep(5)
    print(f'【主函数执行结束于:{time.ctime()}】')

if __name__ == '__main__':
    main()

#运行结果
【主函数执行开始于:Fri Feb 21 16:21:26 2020】
函数执行开始于:Fri Feb 21 16:21:26 2020
函数执行开始于:Fri Feb 21 16:21:26 2020
函数执行结束于:Fri Feb 21 16:21:28 2020
函数执行结束于:Fri Feb 21 16:21:30 2020
【主函数执行结束于:Fri Feb 21 16:21:31 2020

threading模块

该模块在较低级的模块 _thread 基础上建立较高级的线程接口。
1、主要功能
(1)获取当前线程:.current_thread()
(2)构造: .Tread(target=目标函数,args=(参数,))
(3)启动线程:.start()
(4)要求主线程等待:.join()
(5)线程名称:.name
2、构造方法
(1)Thread类方法

import time
import threading

def worker(n):
    print(f'{threading.current_thread().name}函数执行开始于:{time.ctime()}')
    time.sleep(n)
    print(f'{threading.current_thread().name}函数执行结束于:{time.ctime()}')

def main():
    print(f'【主函数执行开始于:{time.ctime()}】')
    threads = []
    t1 = threading.Thread(target=worker, args=(4,))
    threads.append(t1)
    t2 = threading.Thread(target=worker, args=(2,))
    threads.append(t2)
    for t in threads:
        t.start()
    for t in threads:
        t.join()
    print(f'【主函数执行结束于:{time.ctime()}】')

if __name__ == '__main__':
    main()

#运行结果
【主函数执行开始于:Fri Feb 21 17:02:15 2020】
Thread-1函数执行开始于:Fri Feb 21 17:02:15 2020
Thread-2函数执行开始于:Fri Feb 21 17:02:15 2020
Thread-2函数执行结束于:Fri Feb 21 17:02:17 2020
Thread-1函数执行结束于:Fri Feb 21 17:02:19 2020
【主函数执行结束于:Fri Feb 21 17:02:19 2020

(2)自定义Thread派生类

import time
import threading

def worker(n):
    print(f'{threading.current_thread().name}函数执行开始于:{time.ctime()}')
    time.sleep(n)
    print(f'{threading.current_thread().name}函数执行结束于:{time.ctime()}')

class MyThread(threading.Thread):
    def __init__(self,func,args):
        threading.Thread.__init__(self)
        self.func = func
        self.args = args
    def run(self):
        self.func(*self.args)

def main():
    print(f'【主函数执行开始于:{time.ctime()}】')
    threads = []
    t1 = MyThread(worker, (4,))
    threads.append(t1)
    t2 = MyThread(worker, (2,))
    threads.append(t2)
    for t in threads:
        t.start()
    for t in threads:
        t.join()
    print(f'【主函数执行结束于:{time.ctime()}】')

if __name__ == '__main__':
    main()

#运行结果
【主函数执行开始于:Fri Feb 21 17:14:04 2020】
Thread-1函数执行开始于:Fri Feb 21 17:14:04 2020
Thread-2函数执行开始于:Fri Feb 21 17:14:04 2020
Thread-2函数执行结束于:Fri Feb 21 17:14:06 2020
Thread-1函数执行结束于:Fri Feb 21 17:14:08 2020
【主函数执行结束于:Fri Feb 21 17:14:08 2020

同步原语:锁

多线程代码中,如果两个线程运行的顺序发生变化,可能造成代码的执行轨迹或者行为不相同,或者产生不一致的数据。为了避免这种现象,引入锁这一概念。
当多线程争夺锁时,允许第一个获得锁的线程进入临界区,并执行代码;所有之后到达的线程都将被阻塞,直到第一个线程执行结束并释放锁。此时其他的线程可以获得锁并进入临界区。注意:那些被阻塞的线程是没有顺序的(并不是先到先得),意味着下一个获得锁的线程的顺序并不是确定的。
1、获得:.acquire()
2、释放:.release()
3、支持上下文操作:with lock:
例:将1—5依次放入数组中,三个相同的任务同时进行,未使用锁时

import time
import threading
import random

eggs = []

def put_egg(n, lst):
    for i in range(1, n+1):
        time.sleep(random.randint(0,1))
        lst.append(i)

def main():
    threads = []
    for i in range(3):
        t = threading.Thread(target=put_egg,args=(5,eggs))
        threads.append(t)
    for t in threads:
        t.start()
    for t in threads:
        t.join()
    print(eggs)

if __name__ == '__main__':
    main()

#运行结果
[1, 2, 1, 3, 4, 5, 2, 3, 4, 1, 5, 2, 3, 4, 5]

使用锁时

import time
import threading
import random

eggs = []
lock = threading.Lock()

def put_egg(n, lst):
    lock.acquire()
    for i in range(1, n + 1):
        time.sleep(random.randint(0,1))
        lst.append(i)
    lock.release()

def main():
    threads = []
    for i in range(3):
        t = threading.Thread(target=put_egg,args=(5,eggs))
        threads.append(t)
    for t in threads:
        t.start()
    for t in threads:
        t.join()
    print(eggs)

if __name__ == '__main__':
    main()

#运行结果
[1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5]

也可以使用上下文操作:with

def put_egg(n, lst):
    with lock:
        for i in range(1, n + 1):
            time.sleep(random.randint(0,1))
            lst.append(i)

队列 queue模块

queue是python的标准库,俗称队列。队列可以完美解决线程间的数据交换,保证线程间数据的安全性和一致性。
1、队列种类
(1)Queue FIFO:先进先出
(2)LifoQueue LIFO:先进后出
(3)PriorityQueue :优先队列
2、使用方法
(1)构造实例:.Queue(maxsize=0)
(2)放入数据项:.put(item)
(3)获取数据项:.get(block, timeout)
(4)声明当前队列处理完毕:.task_done()
(5)队列所有项处理完毕前阻塞:.join()
例:使用先进先出序列,单线程产生数据放入队列,双线程从队列中取出数据

import time
import threading
import queue
import random

def producer(data_queue):
    for i in range(5):
        time.sleep(0.5)
        item = random.randint(1,100)
        data_queue.put(item)
        print(f'{threading.current_thread().name}在队列中放入数据项{item}')

def consumer(data_queue):
    while True:
        try:
            item = data_queue.get(timeout=3)
            print(f'{threading.current_thread().name}从队列中移除了{item}')
        except queue.Empty:
            break
        else:
            data_queue.task_done()

def main():
    q = queue.Queue()
    threads = []
    p = threading.Thread(target=producer,args=(q,))
    p.start()
    for i in range(2):
        c = threading.Thread(target=consumer,args=(q,))
        threads.append(c)
    for t in threads:
        t.start()
    for t in threads:
        t.join()
    q.join()

if __name__ == '__main__':
    main()

#运行结果
Thread-1在队列中放入数据项28
Thread-2从队列中移除了28
Thread-1在队列中放入数据项60
Thread-3从队列中移除了60
Thread-1在队列中放入数据项42
Thread-2从队列中移除了42
Thread-1在队列中放入数据项79
Thread-3从队列中移除了79
Thread-1在队列中放入数据项92
Thread-2从队列中移除了92

multiprocessing模块

该模块类似于上文的多线程,此外可以充分运用多核、多CPU的计算能力,适用于计算密集型任务。

import time
import multiprocessing

def func(n):
    print(f'{multiprocessing.current_process().name}执行开始于{time.ctime()}')
    time.sleep(n)
    print(f'{multiprocessing.current_process().name}执行结束于{time.ctime()}')

def main():
    print(f'主函数运行于:{time.ctime()}')
    processes = []
    p1 = multiprocessing.Process(target=func,args=(4,))
    processes.append(p1)
    p2 = multiprocessing.Process(target=func, args=(2,))
    processes.append(p2)
    for p in processes:
        p.start()
    for p in processes:
        p.join()
    print(f'主函数结束于:{time.ctime()}')

if __name__ == '__main__':
    main()

#运行结果
主函数运行于:Fri Feb 21 19:16:08 2020
Process-1执行开始于Fri Feb 21 19:16:09 2020
Process-2执行开始于Fri Feb 21 19:16:09 2020
Process-2执行结束于Fri Feb 21 19:16:11 2020
Process-1执行结束于Fri Feb 21 19:16:13 2020
主函数结束于:Fri Feb 21 19:16:13 2020

concurrent.futures模块

concurrent.futures模块提供了使用工作线程或进程池运行任务的接口,线程和进程池API都是一样,所以应用只做最小的修改就可以在线程和进程之间地切换。
1、ThreadPoolExecutor 线程池执行器
2、ProcessPoolExecutor 进程池执行器
例:定义一个函数,分别使用顺序执行、线程池执行及进程池执行

#定义函数
import time
import concurrent.futures

numbers = list(range(1,11))

def count(n):
    for i in range(10000000):
        i += i
    return i*n

def worker(x):
    result = count(x)
    print(f'数字{x}的计算结果是:{result}')

顺序执行

def sequential_execution():
    start_time = time.clock()
    for i in numbers:
        worker(i)
    print(f'顺序执行花费时间{time.clock()-start_time}秒')

if __name__ == '__main__':
    sequential_execution()

#运行结果
数字1的计算结果是:19999998
数字2的计算结果是:39999996
数字3的计算结果是:59999994
数字4的计算结果是:79999992
数字5的计算结果是:99999990
数字6的计算结果是:119999988
数字7的计算结果是:139999986
数字8的计算结果是:159999984
数字9的计算结果是:179999982
数字10的计算结果是:199999980
顺序执行花费时间4.1379074000000005

线程池执行

def threading_execution():
    start_time = time.clock()
    with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
        for i in numbers:
            executor.submit(worker,i)
    print(f'线程池执行花费时间{time.clock() - start_time}秒')

if __name__ == '__main__':
    threading_execution()

#运行结果
数字1的计算结果是:19999998
数字6的计算结果是:119999988
数字2的计算结果是:39999996
数字7的计算结果是:139999986
数字5的计算结果是:99999990
数字4的计算结果是:79999992
数字3的计算结果是:59999994
数字9的计算结果是:179999982
数字8的计算结果是:159999984
数字10的计算结果是:199999980
线程池执行花费时间4.4011857999999995

进程池执行

def process_execution():
    start_time = time.clock()
    with concurrent.futures.ProcessPoolExecutor(max_workers=5) as executor:
        for i in numbers:
            executor.submit(worker,i)
    print(f'进程池执行花费时间{time.clock() - start_time}秒')

if __name__ == '__main__':
    process_execution()

#运行结果
数字1的计算结果是:19999998
数字3的计算结果是:59999994
数字4的计算结果是:79999992
数字5的计算结果是:99999990
数字2的计算结果是:39999996
数字6的计算结果是:119999988
数字8的计算结果是:159999984
数字9的计算结果是:179999982
数字10的计算结果是:199999980
数字7的计算结果是:139999986
进程池执行花费时间1.4596897

未完待续!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值