知识点
-
1 并发编程
-
1.1程序提速手段
-
1.2多任务
-
并发
-
在一个CPU一段时间内交替去执行任务。在各个任务之间快速地切换,给人的感觉就是多个任务在“同时进行”。
-
并行
-
对于多核CPU处理多任务,操作系统会给CPU的每个内核安排一个执行的软件,多个内核分别运行不同的程序。
-
-
1.3进程
-
进程定义
-
一个正在运行的程序或者软件就是一个进程。每个进程都有自己独立的一块内存空间。
-
-
静态--程序 动态--进程是“活着”的程序 地址空间、内存、数据栈、用于跟踪执行的辅助程序
-
-
1.4线程
-
线程定义
-
线程是进程中执行代码的一个分支,是CPU调度和分派的基本单位,一个进程可以有一个或多个线程,各个线程之间共享进程的内存空间。程序中的线程在相同的内存空间中执行,并共享许多相同的资源。
-
-
线程特点
-
-
1.5python对并发编程的支持
-
多线程
-
threading,利用CPU和IO可以同时执行的原理
-
-
多进程
-
multiprocessing,利用多核CPU的能力,真正的并行执行任务。
-
-
异步IO
-
asyncio,利用单线程CPU和IO可以同时执行的原理,实现函数异步执行。
-
-
注意
-
利用Lock对资源加锁,防止冲突访问。
-
使用Queue实现不同线程/进程之间的数据通信,实现生产者-消费者模式
-
使用线程池Pool/进程池Pool,简化任务提交、等待结果、获取结果
-
使用subprocess启动外部程序的进程,并进行输入输出交互
-
-
-
1.6全局解释器锁GIL
-
定义
-
I/O密集型计算采用多线程的方法
-
-
1.7根据任务选择并发技术
-
-
2 多进程
-
2.1四种创建方式
-
os.fork()函数
-
multiprocessing模块Process类创建进程
-
Python的多进程包multiprocessing可以完成从单进程到并发执行的转换,支持子进程、通信和共享数据、执行不同形式的同步,提供了Process、Lock等组件。
-
创建进程
-
Process类,该类可用来在Windows平台上创建新进程。
-
直接创建Process类的实例对象
-
(1)导入进程包
-
(2)Process进程类的说明
-
-
通过继承Process类的子类,创建实例对象。
-
注意,继承Process类的子类需重写父类的run()方法。
-
-
-
Process子类创建进程
-
进程池Pool类创建进程
-
常用方法
-
-
-
2.2进程间通信
-
常用方法
-
管道是双向通信,数据进程不安全,队列是管道加锁来实现的
-
queue类
-
-
-
3 多线程
-
线程
-
进程相当于房子,有(地址空间、内存、数据栈等)浴室、厨房; 线程相当于房子里的居住者,可以使用浴室洗澡,厨房做饭等。
-
3.1创建线程
-
threading模块
-
概述
-
Python的标准库提供了两个模块:_thread和threading,_thread是低级模块,threading是高级模块,对_thread进行了封装。
-
Python标准库模块threading提供了与线程相关的操作:创建线程、启动线程、线程同步。
-
通过创建threading.Thread对象实例,可以创建线程;调用Thread对象的start()方法,可启动线程。也可以创建Thread的派生类,重写run方法,然后创建其对象实例来创建线程。
-
-
函数样式
-
常用方法
-
示例
-
-
说明
-
3.2线程加入join()
-
语法
-
Thread对象.join()
-
-
作用
-
若采用 t.join(),即让包含代码的线程(tc,即当前线程)“加入”到另外一个线程(t)的尾部。在线程(t)执行完毕之前,线程(tc)不能执行。
-
-
说明
-
1. 线程不能加入自己,否则将导致Runtime Error,形成死锁。
-
2. 线程不能加入未启动的线程,否则将导致Runtime Error。
-
-
-
示例
-
-
-
3.3自定义派生于Thread的对象
-
定义
-
通过声明Thread的派生类,创建一个子类,并重写对象的run方法,然后创建其对象实例,可创建线程。通过对象的start方法,可启动线程,并自动执行对象的run方法。
-
-
注意
-
子类的构造器一定要先调用基类的构造器
-
直接将线程需要做的事写到run方法中
-
-
示例
-
-
-
4 锁
-
4.1锁--线程安全
-
定义
-
两种状态
-
两种模式
-
-
线程安全示例
-
背景
-
创建工作线程,模拟银行现金帐户取款。多个线程同时执行取款操作时,如果不使用同步处理,会造成账户余额混乱;尝试使用同步锁对象Lock,以保证多个线程同时执行取款操作时,银行现金帐户取款的有效和一致。
-
-
代码
-
-
-
-
5 生产者-消费者模型
-
5.1生产者和消费者模型
-
5.2queue模块
-
两种通信
-
概念
-
使用Python标准模块queue提供了适用于多线程编程的先进先出的数据结构(即队列),用来在生产者和消费者线程之间的信息传递。使用queue模块中的线程安全的队列,可以快捷实现生产者和消费者模型。
-
-
主要方法
-
(1)Queue(maxsize=0):构造函数,构造指定大小的队列。默认不限定大小
-
(2)put(item, block=True, timeout=None):向队列中添加一个项。默认阻塞,即队列满的时候,程序阻塞等待
-
(3)get(block=True, timeout=None):从队列中拿出一个项。默认阻塞,即队列为空的时候,程序阻塞等待
-
-
图示
-
-
基于queue.Queue的生产者和消费者模型
-
-
6 小结
-
线程是最小的执行单元,而进程由至少一个线程组成。如何调度进程和线程,完全由操作系统决定,程序自己不能决定什么时候执行,执行多长时间。
-
通过创建threading.Thread对象实例,可以创建线程;调用Thread对象的start()方法,可启动线程。也可以创建Thread的派生类,重写run方法,然后创建其对象实例来创建线程。
-
Lock对象锁解决线程安全问题:acquire()获得锁;release() 释放锁
-
queue模块提供了线程之间的数据传递:put()添加元素;get()获取元素
-
实验
一、实验目的:
1. 掌握pycharm的使用
2. 掌握python的面向对象的程序设计概念
3. 掌握多线程的使用
4. 理解python多线程互斥锁
二、实验环境
本次实验需使用实验室提供的Windows主机环境+pycharm
三、实验内容
说明:基础题为必做题,提高题为选做题
1.(基础题)编写程序实现:创建T1、T2、T3三个线程,怎样保证线程的执行顺序为T2->T1->T3?
import time
import threading
class TestThread(threading.Thread):
def __init__(self, thread_name):
super(TestThread, self).__init__()
self.thread_name = thread_name
def run(self):
time.sleep(2)
print('Thread:%s\t开始执行' % self.thread_name)
if __name__ == "__main__":
# 编写代码
import time
import threading
class TestThread(threading.Thread):
def __init__(self, thread_name):
super(TestThread, self).__init__()
self.thread_name = thread_name
def run(self):
time.sleep(2)
print('Thread:%s\t开始执行' % self.thread_name)
if __name__ == "__main__":
t1 = TestThread("T1")
t2 = TestThread("T2")
t3 = TestThread("T3")
t2.start() # 首先启动T2
t2.join() # 等待T2执行完成
t1.start() # 然后启动T1
t1.join() # 等待T1执行完成
t3.start() # 最后启动T3
t3.join() # 等待T3执行完成
2. (基础题)将下列程序进行修改,要求每次运行结果输出样式为:(注:采用lock)
import threading,time
def job1():
global A
for i in range(10):
A += 1
print('job1',A)
def job2():
global A
for i in range(10):
A += 10
print('job2',A)
if __name__ =='__main__':
A = 0
t1 = threading.Thread(target=job1)
t2 = threading.Thread(target=job2)
t1.start()
t2.start()
t1.join()
t2.join()
import threading
import time
def job1():
global A
lock.acquire()
for i in range(10):
A += 1
print('job1', A)
lock.release()
def job2():
global A
lock.acquire()
for i in range(10):
A += 10
print('job2', A)
lock.release()
if __name__ == '__main__':
A = 0
lock = threading.Lock() # 创建锁对象
t1 = threading.Thread(target=job1)
t2 = threading.Thread(target=job2)
t1.start()
t2.start()
t1.join()
t2.join()
3.(提高题)编写程序实现:使用多线程实现一个模拟银行取钱的程序,要求:
- 使用全局变量作为余额;
- 存取钱时都只能有一个线程访问全局变量
- 取钱线程和存钱线程同时运行,每次存取随机数量的金额,直到取钱余额不足时则打印出相关信息,并结束程序。
import threading
import time
import random
class BankAccount:
def __init__(self, balance):
self.balance = balance
self.lock = threading.Lock()
def deposit(self, amount):
with self.lock:
time.sleep(random.random()) # 模拟处理时间
self.balance += amount
print(f"存入{amount}元,当前余额:{self.balance}元")
def withdraw(self, amount):
with self.lock:
time.sleep(random.random()) # 模拟处理时间
if self.balance >= amount:
self.balance -= amount
print(f"取出{amount}元,当前余额:{self.balance}元")
else:
print(f"余额不足,无法取出{amount}元")
return False
return True
def deposit_thread(account):
while True:
amount = random.randint(100, 500)
account.deposit(amount)
def withdraw_thread(account):
while True:
amount = random.randint(50, 300)
if not account.withdraw(amount):
break
if __name__ == "__main__":
account = BankAccount(1000)
deposit_thread = threading.Thread(target=deposit_thread, args=(account,))
withdraw_thread = threading.Thread(target=withdraw_thread, args=(account,))
deposit_thread.start()
withdraw_thread.start()
deposit_thread.join()
withdraw_thread.join()