Bootstrap

python--实验14 并发编程(多线程)

知识点 

 

  • 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.(提高题)编写程序实现:使用多线程实现一个模拟银行取钱的程序,要求:

  1. 使用全局变量作为余额;
  2. 存取钱时都只能有一个线程访问全局变量
  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()

 

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;