21天学习挑战赛Python学习第四篇:多线程 threading 模块

Posted Goodric

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了21天学习挑战赛Python学习第四篇:多线程 threading 模块相关的知识,希望对你有一定的参考价值。

【21天学习挑战赛】Python学习第四篇:多线程 threading 模块

——

活动地址:CSDN21天学习挑战赛

——

多线程的理解就是两件或两件以上的事情通过代码同时发生。
而一般情况下我们写python代码的话是从上往下执行的,有一个先后顺序。

比如执行一下两行代码,就是先打印123 ,再打印456,而不是同时执行

print('123')
print('456')

而现在要学习实现的多线程就是要能够多个任务同时进行。

我们使用的 cpu 就可以执行多任务, 单核CPU 是操作系统轮流让各个任务交替执行, 任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务1执行0.01秒, 这样反复执行下去。 表面上看,每个任务都是交替执行的, 但由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样。

如今大多使用多核CPU,在 任务数量远远多于CPU的核心数量情况下, 操作系统会自动把很多任务轮流调度到每个核心上执行。

两个名词概念:
并发
不是真正意义上的同时执行, 是任务数比cpu核数多,通过操作系统的各种任务调度算法,像前面讲的轮流执行,实现用多个任务“一起”执行,(实际上总有一些任务不在执行,因为切换任务的速度相当快,看上去一起执行而已)
并行
任务数小于等于cpu核数,所有任务真的一起执行。

——

threading模块的使用

Python的 Thread 模块是比较底层的模块,python的 threading 模块是对 Thread 做了一些包装的,可以更加方便的被使用

threading模块常用方法
threading.active_count(): 返回当前处于active状态的Thread对象
threading.current_thread(): 返回当前Thread对象
threading.get_ident(): 返回当前线程的线程标识符。线程标识符是一个非负整数,并无特殊含义,只是用来标识线程,该整数可能会被循环利用。
threading.enumerate(): 返回当前处于active状态的所有Thread对象列表
threading.main_thread(): 返回主线程对象,即启动Python解释器的线程对象。Python3.4及以后版本支持该方法
threading.stack_size(): 返回创建线程时使用的栈的大小,如果指定size参数,则用来指定后续创建的线程使用的栈大小,size必须是0(表示使用系统默认值)或大于32K的正整数

——

thread 类

threading模块提供了Thread、Lock、RLock、Condition、Event、TimerSemaphore等类来支持多线程,Thread是其中最重要也是最基本的一个类,可以通过该类创建线程并控制线程的运行。

threading.Thread 的方法和属性:
start(): 启动线程。
run(): 线程代码,用来实现线程的功能与业务逻辑,可以在子类中重写该方法来自定义线程的行为
init(self, group=None, target=None, name=None, args=(), kwargs=None, daemon=None)
构造函数
is_alive(): 判断线程是否存活
getName(): 返回线程名
setName(): 设置线程名
isDaemon(): 判断线程是否为守护线程
setDaemon(): 设置线程是否为守护线程
name: 用来读取或设置线程的名字
ident: 线程标识,用非0数字或None(线程未被启动)
daemon: 表示线程是否为守护线程,默认为False
join(timeout=None): 当 timeout 为 None 时,会等待至线程结束;当 timeout 不为 None 时,会等待至 timeout 时间结束,单位为秒。

使用thread 创建线程

语法格式:
threading.Thread(group=None,target=None,name=None,args=(),kwargs=,*,daemon=Nome)

参数:
group: 通常默认即可,作为日后扩展 ThreadGroup 类实现而保留。
target: 用于 run() 方法调用的可调用对象,默认为 None。
name: 线程名称,默认是 Thread-N 格式构成的唯一名称,其中 N 是十进制数。
args: 用于调用目标函数的参数元组,默认为 ()。
kwargs: 用于调用目标函数的关键字参数字典,默认为 。
daemon: 设置线程是否为守护模式,默认为 None。

threading:Thread 的方法和属性
start(): 启动线程。

——

实例化 threading.Thread

正常单线程:

import threading
import time

def printt():
    print("正在打印-")
    time.sleep(1)

if __name__ == "__main__":
    for i in range(5):
        printt()

执行五次printt() 函数,过一秒打印一次,打印五次需要五秒。

——

使用threading模块

def printt():
    print("正在打印-")
    time.sleep(1)

if __name__ == "__main__":
    for i in range(5):
        t = threading.Thread(target=printt)
        t.start()

调用start()时,才会真正的创建线程,并且开始执行
五条打印会同时出现。

主线程会等待所有子线程结合后才结束

import threading
import time

def sing():
    for i in range(3):
        print("正在sing%d"%i)
        time.sleep(2)

def dance():
    for i in range(3):
        print("正在dance%d"%i)
        time.sleep(2)

if __name__ == "__main__":
    t1 = threading.Thread(target=sing)
    t2 = threading.Thread(target=dance)
    t1.start()
    t2.start()
    print("------结束------")

两条两条打印,执行了第一个双线程,然后就打印"结束",再去执行线程中的循环打印。

实时线程数量查看

import threading
import time

def sing():
    for i in range(3):
        print("正在sing%d"%i)
        time.sleep(1)

def dance():
    for i in range(3):
        print("正在dance%d"%i)
        time.sleep(1)

if __name__ == "__main__":
    t1 = threading.Thread(target=sing)
    t2 = threading.Thread(target=dance)
    t1.start()
    t2.start()
    
    while True:
        length = len(threading.enumerate())
        print("当前运行线程数:%d"%length)
        if length<=1:
            break
        time.sleep(1)

运行过程:

——

继承 threading.Thread

使用threading模块能完成多任务的程序开发,为了让每个线程的封装性更完美,所以使用threading模块时,往往会定义一个新的子类class,只要继承 threading.Thread 就可以了,然后重写run方法

import threading
import time

class MyThread(threading.Thread):
    def run(self):
        for i in range(3):
            time.sleep(1)
            msg = self.name + ' @ ' + str(i)
            print(msg)

if __name__ == "__main__":
    t=MyThread()
    t.start()

threading.Thread 类有一个 run 方法,用于定义线程的功能函数,可以在自己的线程类中覆盖该方法。而创建自己的线程实例后,通过 Thread 类的 start 方法,可以启动该线程,交给python虚拟机进行调度,当该线程获得执行的机会时,就会调用 run 方法执行线程。

线程执行顺序查看

import threading
import time

class MyThread(threading.Thread):
    def run(self):
        for i in range(3):
            time.sleep(1)
            msg = self.name + ' @ ' + str(i)
            print(msg)

def test():
    for i in range(5):
        t=MyThread()
        t.start()

if __name__ == "__main__":
    test()

可以每五个同时输出,并且 多线程程序的执行顺序是不确定的。
代码中只能保证每个线程都运行完整个run函数,但是线程的启动顺序、run函数中每次循环的执行顺序都不能确定。
当线程的**run()**方法结束时该线程完成。

——

互斥锁和死锁

应该是在"数据结构"这门课程有。

互斥锁

当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制

某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。

优点:

确保了某段关键代码只能由一个线程从头到尾完整地执行

缺点:

阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了
由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁

——

死锁

在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。

比如总共有资源1资源2线程1先占用了资源1线程2先占用了资源2,但是现在线程1线程2要进行下去都还缺少了资源。
此时线程1缺少资源2线程2缺少资源1,但是缺少的资源都被对方占用了,双方都在等待对方释放自己需要的那部分资源,但是双方线程没有完成执行完都不会进行释放资源,导致进入了持续的等待状态,即死锁状态,程序无法继续运行下去。

避免死锁

优化程序设计,合理分配资源,比如有银行家算法,根据优先级分配资源。
添加超时时间,等待资源的过程如果超过时间,则自动结束,释放资源。

以上是关于21天学习挑战赛Python学习第四篇:多线程 threading 模块的主要内容,如果未能解决你的问题,请参考以下文章

python 学习_第四模块 并发编程(多线程)

21天学习挑战赛—Python学习记录第一篇

21天学习挑战赛—Python学习记录第一篇

21天学习挑战赛Python学习第二篇:json标准库

21天学习挑战赛Python学习第二篇:json标准库

21天学习挑战赛Python学习第三篇:os 标准库