并发编程之多线程操作篇

多线程简单介绍

多线程,或者说多任务,指的是操作系统同时运行多个任务。例如,听歌、洗衣服、看视频可以同时进行,这种就是多任务。

单核CPU执行多任务:操作系统轮流让各个任务交替执行,任务1执行t1时间,切换到任务2,任务2执行t2时间,再切换到任务3,执行t3时间...如此反复执行,表面上看,每个任务交替执行,但是由于CPU的执行速度很快,在人看来就好像所有任务同时执行。

这里需要注意并发和并行的概念:

  • 并发,指任务数多于COU核数,通过操作系统的各种任务调度算法,实现用多个任务一起执行(实际上有一些任务没有在执行,但因为切换任务的速度相当快,看上去一起执行)。
  • 并行,指的是任务数小于或等于CPU核数,任务是同时执行的。

threading模块介绍

python中提供了thread和threading模块对线程进行操作,其中thread模块是比较底层的模块,threading模块对thread做了一些包装,使用更方便。

threading模块常用方法

方法 功能
threading.active_count() 返回当前处于active状态的Thread对象的个数
threading.current_thread() 返回当前Thread对象
threading.get_ident() 返回当前线程的线程标识
threading.enumerate() 返回当前处于active状态的所有Thread对象列表
threading.main_thread() 返回主线程对象,启动python解释器的线程对象
threading.stack_size() 返回创建线程时使用的栈的大小,如果指定 size参数,则用来指定后续创建的线程使用的 栈大小,size必须是0(标识使用系统默认值) 或大于32K的正整数

:线程标识是一个非负整数,并无特殊含义,只是用来标识线程,该整数可能会被循环利用,python3.3及以后版本支持该方法。

threading模块提供了Thread、Lock、RLock、Condition、Event、Timer和Semaphore等类支持多线程。

Thread类使用

Thread是threading提供的最重要也是最基本的类,可以通过该类创建线程并控制线程的运行。

使用Thread创建线程,有两种方式:

为构造函数传递一个可调用对象
继承Thread类并在子类中重写__init__()和run()
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时,会等待至线程结束;

threading.Thread类的参数介绍:

threading.Thread(group=None,
    target=None,
    name=None,
    args=(),
    kwargs={},
    *,
    daemon=None)
参数 group 通常默认,作为扩展ThreadGroup类保留。
参数 target 用于run()方法调用的可调用对象,默认为None,标识run()不调用任何对象。
参数 name 线程名称,默认是Thread-N格式狗策划给你的唯一名称,N是十进制数。
参数 args  用于调用目标函数的参数元组,默认是()。
参数 kwargs  用于调用目标函数的关键字参数字典,默认为{}。
参数 daemon 设置线程是否为守护模式,默认为None。

开启线程的两种方式

  • 开启线程不需要在main下面执行代码,直接书写即可
  • 但是我们还是习惯性的将启动命令写在main下面

方式一:使用构造函数传递可调用对象的方法创建线程

示例1 :使用threading模块创建单线程

import threading
import time


def sing():
    print("正在唱歌...")
    time.sleep(1)


if __name__ == "__main__":
    print("======================调用函数======================")
    start_time = time.time()
    print("开始时间:%s" % time.ctime())
    for i in range(10):
        sing()
    print("结束时间:%s" % time.ctime())
    print(f"总共耗时{time.time() - start_time}s")
    print("==========threading.Thread.start()开启单线程==========")

    print("开始时间:%s" % time.ctime())
    start_time = time.time()
    for i in range(10):
        t = threading.Thread(target=sing)
        t.start()
    print("结束时间:%s" % time.ctime())
    print(f"总共耗时{time.time() - start_time}s")

显示结果,可以看出来使用threading.Thread启动线程时,使用的时间要短很多。

======================调用函数======================
开始时间:Fri Jan 19 16:36:43 2024
正在唱歌...
正在唱歌...
正在唱歌...
正在唱歌...
正在唱歌...
正在唱歌...
正在唱歌...
正在唱歌...
正在唱歌...
正在唱歌...
结束时间:Fri Jan 19 16:36:53 2024
总共耗时10.00361943244934s
==========threading.Thread.start()开启单线程==========
开始时间:Fri Jan 19 16:36:53 2024
正在唱歌...
正在唱歌...
正在唱歌...
正在唱歌...
正在唱歌...
正在唱歌...
正在唱歌...
正在唱歌...
正在唱歌...
正在唱歌...
结束时间:Fri Jan 19 16:36:53 2024
总共耗时0.0010197162628173828s

当调用start()时,才开始真正的创建线程,并开始执行。

示例2:使用threading.Thread创建多线程,并查看当前处于active状态的线程的数量。

import threading
from time import ctime, sleep


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


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


if __name__ == "__main__":
    print("---start---:%s" % ctime())

    t1 = threading.Thread(target=sing)
    t2 = threading.Thread(target=dance)

    t1.start()
    t2.start()

    while True:
        length = len(threading.enumerate())
        print("current thread has %d" % length)
        if length <= 1:
            break

        sleep(0.5)

    print("---end---:%s" % ctime())

结果:

---start---:Fri Jan 19 16:43:22 2024
singing...0
dancing...0
current thread has 3
current thread has 3
dancing...1
singing...1
current thread has 3
current thread has 3
singing...2dancing...2

current thread has 3
current thread has 3
current thread has 1
---end---:Fri Jan 19 16:43:25 2024

方式二:继承threading.Thread类

在使用threading模块时,也可以定义一个新的类class,继承threading.Thread,然后重写run()。

继承threading.Thread来定义线程类,其本质是重构Thread类中的run方法

threading.Thread类中的run方法,用于定义线程的功能函数,可以在自己的线程类中覆盖该方法。创建自己的线程实例后,通过Thread类的start方法,可以启动该线程。

示例1:创建一个继承自threading.Thread类的子类

import threading
import time


class MyThread(threading.Thread):
    def run(self):
        for i in range(3):
            time.sleep(1)
            msg = "I'm " + self.name + " | " + str(i)  # name保存当前线程的名字
            print(msg)


if __name__ == "__main__":
    t = MyThread()
    t.start()
    
# I'm Thread-1 | 0
# I'm Thread-1 | 1
# I'm Thread-1 | 2

示例2:观察线程执行的顺序

import threading
import time


class MyThread(threading.Thread):
    def run(self):
        for i in range(3):
            time.sleep(1)
            msg = "I'm " + self.name + " | " + str(i)  # name保存当前线程的名字
            print(msg)


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


if __name__ == "__main__":
    test()
    
# I'm Thread-2 | 0
# I'm Thread-1 | 0
# I'm Thread-4 | 0
# I'm Thread-5 | 0
# I'm Thread-3 | 0
# I'm Thread-1 | 1
# I'm Thread-2 | 1
# I'm Thread-3 | 1
# I'm Thread-5 | 1
# I'm Thread-4 | 1
# I'm Thread-1 | 2
# I'm Thread-2 | 2
# I'm Thread-5 | 2
# I'm Thread-3 | 2
# I'm Thread-4 | 2

每次运行时显示的执行顺序是不确定的。当执行到sleep()的时候,当前线程被阻塞,sleep()结束后当前线程进入就绪状态,等待调度。

线程调度会自行选择一个线程执行。

如上代码只能保证每个线程都能运行完整个run函数,但线程的启动顺序、run函数中每次循环的执行顺序都不能确定。

总结

  1. 每个线程默认有一个名字,没有指定,python会自动为线程指定一个名字
  2. 当线程的run()方法结束时该线程完成;
  3. 无法控制线程调度程序,可以通过别的方式影响线程调度方式。

线程对象的 join 方法

from threading import Thread
import time


def task(name):
    print(f'the task {name} is beginning')
    time.sleep(3)
    print(f'the task {name} is ending')


def main():
    t = Thread(target=task, args=('xiao',))
    t.start()

    # 主线程等待子进程结束之后再运行
    t.join()

    print(f'the task is main task')


if __name__ == '__main__':
    main()
    
    # the task xiao is beginning
    # the task xiao is ending
    # the task is main task

同一个进程下的多个线程之间数据是共享的

from threading import Thread
import time

money = 999


def task():
    global money
    money = 99
    print(f'task中的money:>>>>{money}')


def main():
    print(f'子进程之前的money:>>>>{money}')
    t = Thread(target=task)
    t.start()
    print(f'子进程之后的money:>>>>{money}')


if __name__ == '__main__':
    main()

    # 子进程之前的money:>>>>999
    # task中的money:>>>>99
    # 子进程之后的money:>>>>99
posted @ 2024-01-21 21:55  Xiao0101  阅读(51)  评论(0)    收藏  举报