python3.0的多线程

版权声明:CSDN 博主刘康康 https://blog.csdn.net/liiukangkang/article/details/83023377

1.进程

进程:计算机中一个程序在一个数据集上一次动态执行过程,主要包含三部分内容
⚫ 程序:描述进程的功能以及处理流程
⚫ 数据集:功能处理过程中需要的资源数据
⚫ 进程控制:严格控制进程执行过程中的各种状态
一个软件程序要运行,需要将软件依赖的数据加载到内存中,通过 CPU 进行运算并按照程 序定义的逻辑结构进行流程控制,知道数据处理完成后程序退出! 在程序实际执行过程中,进程只是分配需要的数据资源,是程序的主体,在程序运行时真正 运行的是线程,每个进程至少会有一个线程。

2.线程(Thread)

计算机中程序运行的实际执行者就是线程,线程又称为轻量级进程,是一个 CPU 的执行单 元,每个进程至少会有一个主线程用于执行程序
线程和进程对比如下
⚫ 一个进程可以有多个线程,但是至少有一个主线程
⚫ 一个线程只能属于一个进程
⚫ 一个进程中多个线程,可以共享进程中提供的数据
⚫ CPU 运算分配给线程,CPU 上执行运算的是线程
⚫ 线程是最小的运行单元,进程是最小的资源管理单元

2.1 串行、并行、并发

什么是并发?
所谓并发:表面上是同时执行,并发是相对于顺序执行的概念
并发:在很短的时间之类,多个任务切换执行,在一个时间段~体现出来一种几个任务同时执行的表象,并发,在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,担任一个时刻点只有一个程序在处理机上运行。
并行:(以可同时执行的计算指令方式编程)
串行:串行程序中,程序会按照顺序执行每一条指令,在整个程序运行过程中,仅存在一个运行上下文。(即一个调用栈,一个堆)
串行:就是传统意义上的同步、顺序的意思,按照一定的执行步骤顺序执行每个环节

并行:就是传统意义上的异步、同时的意思,同时执行接受到的多个任务

并发:同时接收到多个任务,同时执行多个任务,但是具体到某个时刻~只是在执行一个任 务,只是在很短时间内在多个任务之间切换,模拟形成了多个任务同时执行的现象 而大部分计算机中的应用程序的执行,一般都是并发执行机制的多任务处理机制
在这里插入图片描述

2.2多线程编程

PYTHON 本身对于多线程并发机制的支持是比较完善的
在 PYTHON2 中提供了标准模块 thread 和 threading 支持多线程的并发编程 但是随着并发编程的实际使用操作过程,thread 模块过于底层的控制方式对于并发编程的 新手来说挺不是非常友好,要求多线程的程序开发逻辑思维清晰同时又具备大量开发经验的 情况下,可以控制的非常精细。
在PYTHON3 中将 thread 模块进行了规范内置,更名为_thread,友好的提醒如果你不是并发编 程的骨灰级爱好者,请不要轻易尝试使用_thread 进行操作,而是推荐使用操作更加灵活使 用更加简洁的 threading 模块进行并发编程的处理。

2.2.1 PYTHON 中的多线程

官方推荐的_threading 模块的多线程并发编程机制,结合时下流行的面向过程/面向对象的 编程处理模式,主要有两种操作方式
⚫ 函数式的线程创建方式,适合面向过程程序的并发编程实现
⚫ 面向对象的创建方式,适合面向对象程序的并发编程实现
threading 模块属性和方法

名称 描述
Thread 线程类,用于创建和管理线程
Event 事件类,用于线程同步
Condition 条件类,用于线程同步
Lock/RLock 锁类,用于线程同步
Timer 延时线程,用于在一定事件后执行一个函数
Semaphore/BoundedSemaphore 信号量类,用于线程同步
active_count()/activeCount() 获取当前 alive 状态的所有线程数量 current
current_thread()/currentThread() 获取当期正在执行的线程对象
get_ident() 获取运行中程序当前线程的唯一编号
enumerate() 获取所有 alive 状态线程列表
local 线程局部数据类
stack_size([size]) 获取线程占用内存栈的大小
main_thread() 获取主线程

2.2.2Thread 类型属性和方法

名称 描述
init(group,target,name,args,kwargs) 构造方法,创建线程类型
is_alive()/isAlive() 判断当前线程是否 alive 状态
run() 线程执行方法,自定义线程必须重写该函数
start() 线程启动方法
join([timeout=None]) 线程独占,等待当前线程运行结束或者超时
ident 标识当前线程的唯一编号 name 当前线程名称
daemon 布尔值,判断当前线程是否守护线程

⚫多线程售票:相当于多个窗口同时售票(简单)
代码:

"""
desc 多线程并发
version 1.1.0
author lkk
email [email protected]
"""
import threading, time

ticket = 10


def sale_ticket():
    global ticket
    while ticket > 0:
        print(threading.current_thread().name, '{}号票已经售出'.format(ticket))
        ticket -= 1
        time.sleep(1)


if __name__ == '__main__':

    S1 = threading.Thread(target=sale_ticket, name='窗口1')
    S2 = threading.Thread(target=sale_ticket, name='窗口2')

    S1.start()
    S2.start()
    start = time.clock()
    elapsed = (time.clock() - start)
    print("程序耗时:", elapsed)

程序运行要比单线程程序运行效率大大提高

2.2.3线程状态

join:线程的 join 状态是独占模式,当前线程独占 CPU 运行单元,必须等待当前线程执行完 成或者超时之后,才能运行其他线程 。

"""
desc 线程状态join
version 1.1.0
author lkk
email [email protected]
"""

# 引入模块
import threading, time

count = 10


def sale_ticket():
    global count
    while count > 0:
        print(threading.current_thread().getName(), "售出一张票:", count)
        count -= 1
        time.sleep(0.5)
    else:
        print(threading.current_thread().getName(), "没有票了")


if __name__ == "__main__":  
    
    # 定义多个线程 ( 窗口 )     
    t1 = threading.Thread(name="窗口 1", target=sale_ticket)     
    t2 = threading.Thread(name="窗口 2", target=sale_ticket)     
    t3 = threading.Thread(name="窗口 3", target=sale_ticket)     
    t4 = threading.Thread(name="窗口 4", target=sale_ticket)    
    t5 = threading.Thread(name="窗口 5", target=sale_ticket) 

# 启动五个窗口同时售票     
    t1.start()     
#  线程 t1 调用 join ,独占模式运行,等待 t1 线程运行结束或者超时,才能继续运行其他线程     
    t1.join()     
    t2.start()     
    t3.start()     
    t4.start()     
    t5.start() 

2.2.4线程管理-锁[Lock/RLock]

多线程程序在运行过程中,由于多个线程访问的是同一部分数据,很容易会造成共享数据访 问冲突的现象,如果一旦出现冲突程序就会出现执行结果不符合期望的结果

修改售票程序,两个窗口同时售票,修改代码如下

"""
desc 多线程并发
version 1.1.0
author lkk
email [email protected]
"""
import threading, time,os,sys

ticket = 10


def sale_ticket():
    global ticket
    while 1:
        if lock.acquire():
            if ticket > 0:
                print(threading.current_thread().name, '{}号票已经售出'.format(ticket))
                time.sleep(0.2)
                ticket -= 1
            else:
                print(threading.current_thread().name, '票已经售空')
            lock.release()


if __name__ == '__main__':
    lock = threading.Lock()
    S1 = threading.Thread(target=sale_ticket, name='窗口1')
    S2 = threading.Thread(target=sale_ticket, name='窗口2')

    S1.start()
    S2.start()

此时共享数据的修改操作,在多线程的情况下,是需要通过锁定的方式进行独占修改的! 就如同如厕一样,当多个人[线程]在执行程序,修改数据[如厕]时,每个线程在操作过程中都 需要锁定这一部分数据[厕所],直到数据处理完成之后解锁,下一个线程才能进行操作

python 中提供了两种线程锁的操作
⚫ 同步锁/互斥锁:Lock
⚫ 可重用锁:RLock

锁的操作主要是获取锁和释放锁两种
⚫ acquire() 获取锁,上锁,锁定
⚫ release() 释放锁,开锁,解锁

2.2.5线程管理-死锁[Dead Lock]

线程锁固然功能强大,可以管理多个线程之间的共享数据问题 但是同时它的强大也带来了比较纠结的问题,需要开发人员对于锁定的数据有一个良好的认 知,否则特别容易造成死锁的现象,比较著名的哲学家吃饭问题就是死锁的典型代表

由于计算机运算速度较快,所以有两种方案可以将问题放大
⚫ 给执行函数添加休眠时间
⚫ 添加线程数量
死锁并不是每次都会出现的,而是程序在执行过程中,根据系统 CPU 时间片的切换机制恰 好遇到了重复上锁的情况,就会死锁
实际项目开发过程中,一定要注意死锁情况的影响 这样的情况可以通过可重用锁 RLock 进行锁定处理!

PYTHON 中的互斥锁,只有两种状态,locked 和 unlocked,如果一旦重复上锁就会死锁 但是可重用锁 RLock,在锁定的基础上提供了一个计数器 counter,可以计算上锁的次数然 后通过 release()解锁时就会重新运算计数器,等待计数器清零时所有锁全都释放了

import threading, time 
def zhexuejia1():    
 	if lock1.acquire():         
 		if lock2.acquire():             
        	print("你给我叉子,我就给你刀子")             
			lock2.release()        
  		lock1.release() 
def zhexuejia2():    
  		if lock2.acquire():         
  			if lock1.acquire():                     
  				print("你给我刀子,我就给你叉子")             
  				lock1.release()         
  			lock2.release() 
 
if __name__ == "__main__": 
    lock1 = threading.Lock()     
    lock2 = threading.Lock() 
    for i in range(1000):         
	    zxj = threading.Thread(name="哲学家 A" + str(i), target=zhexuejia1)         
	    zxj2 = threading.Thread(name="哲学家 B" + str(i), target=zhexuejia2)         
	    zxj.start()         
	    zxj2.start()

猜你喜欢

转载自blog.csdn.net/liiukangkang/article/details/83023377