Python多任务实现方式(二)---多线程

95 阅读4分钟

前言:前面讲到了多进程,那么本博主今天来深剖实现多任务的第二种方式:线程(threading)

一.引入线程:

众所周知传统的进程有两个基本属性:

  1. 可拥有资源的独立单位;
  2. 可独立调度和分配的基本单位;

引入线程的基本原因是,进程在创建、·切换·和·撤销·时,系统必须为之付出·较大的内存开销·,所以在系统中设置的进程数量不宜过多,进程的切换频率不宜太高,这就·限制·了·并发程度·的提高。引入线程后,将传统进程的两个基本属性分开,线程作为调度和分配的基本单位,进程作为独立分配资源的单位。用户可以创建线程来完成任务,以减少程序并发执行时付出的内存开销。

  1. 进程给线程目标,线程去完成这个目标,
  2. 一个程序执行必然有一个线程(也就是主线程),一个程序执行必然有一个进程(主进程)。

二.普通程序与线程:.

1.普通程序举例:

假定我们在执行程序时,会构造很多函数,但是如果每个函数都要完成自己的事情,在普通程序中,只有当一个完成时才会执行另一个函数,这样子会造成内存过度开销,例如:

import time
def run():
    for _ in range(5):
        time.sleep(1)
        print("我会跑")
def sing():
    for _ in range(5):
        time.sleep(1)
        print("我会唱歌")
if __name__ == '__main__':
    run()
    sing()        

我们简单的定义了两个函数,各有各的事情要做,但是这种情况只有当run()函数执行完才会去执行sing()函数,这样程序会一直等待,会造成内存不必要的开销,结果如下图: 在这里插入图片描述

2.线程执行:

线程引入需要导包,也就是threading这个包,thread模块是比较底层的模块,threading是对thread包装。所以导入threading,我们先来看一下区别,然后我们来看看线程的声明以及开启;

import  threading
import time
def run():
    for _ in range(5):
        time.sleep(1)
        print("我会跑")
def sing():
    for _ in range(5):
        time.sleep(1)
        print("我会唱歌")
if __name__ == '__main__':
    run_thread=threading.Thread(target=run)
    sing_thread=threading.Thread(target=sing)
    run_thread.start()
    sing_thread.start()

在这里插入图片描述

三.线程(threading):

通过上面的举例我们都已经看到了两者的区别,线程会各干各的事,节约了不少内存空间,但是怎么使用呢--------------------------我们来看看:

1.threading.Thread():

和进程一样创建一个Thread对象,来进行创建这个线程,Thread的参数有:

  • group:指定进程组,一般就默认为None;
  • target:执行目标任务名;
  • name:进程名字;
  • args:以元组形式向执行任务传参;
  • kwargs:以字典形式向执行任务传参.
  • daemon:守护程序,默认值为None;

举例:

run_thread=threading.Thread(target=run)
sing_thread=threading.Thread(target=sing)

在前面进行定义的方法就可以在这儿通过此方法进行创建哦

2.start():

和进程中一样都是用来开启进程的,例如我们在面创建了两个线程,那么在此就可以进行开启:

run_thread.start()
sing_thread.start()

3.join():

当主线程执行完再执行子线程,和进程一样:

    run_thread.start()
    run_thread.join()
    sing_thread.start()

在这里插入图片描述

四.查看线程数量:

当线程有点多的时候,我们可以通过enumerate()来进行查看线程的数量,如下:

import  threading
import time
count=0
def run():
    for _ in range(5):
        time.sleep(1)
        print("我会跑")
    print(threading.enumerate())
def sing():
    for _ in range(5):
        time.sleep(1)
        print("我会唱歌")
    print(threading.enumerate())

if __name__ == '__main__':
    # run()
    # sing()
    run_thread=threading.Thread(target=run)
    sing_thread=threading.Thread(target=sing)
    run_thread.start()
    sing_thread.start()
    print(threading.enumerate())

在这里插入图片描述 我们可以看到,图中总共有三个线程,一个主线程,两个子线程,并且有Thread-x来进行标识

五.线程特点:

  1. 线程执行代码的封装,通过threading这个包中的Thread功能,往往会定义一个新的子类Class来继承:threading.thread,一般情况下线程的主入口函数为run(),我们只不过对其进行重写而已:

import  threading
class threa(threading.Thread):
    def run(self):
        for _ in range(5):
            time.sleep(1)
            print("我会跑")
        print(threading.enumerate())
if __name__ == '__main__':
    # run()
    # sing()
    a=threa()
    run_thread=threading.Thread(target=a.run)
    run_thread.start()
    ret=len(threading.enumerate())
    print(ret)

在这里插入图片描述

  1. 多线程共享全局变量: 对全局变量进行修改时,判断是否对全局变量进行引用修改,如果修改了引用然后执行,让全局变量指向了另一个新地方,如果修改了引用的数据,则不必担心变量被分化:

举例:

import  threading
import time
count=0
def sum():
    for i in range(10000):
        global count
        count+=1
    print(f"我是第一个:{count}")
def sum1():
    for i in range(10000):
        global count
        count+=1
    print(f"我是第二个:{count}")
if __name__ == '__main__':
    # run()
    # sing()
    sum_thread=threading.Thread(target=sum)
    sum1_thread = threading.Thread(target=sum1)
    sum_thread.start()
    sum1_thread.start()
    # run_thread.start()
    # run_thread.join()
    # sing_thread.start()
    print(f"总计为:{count}")    

在这里插入图片描述

由上图可以看出,线程是共享全局变量的,但是修改全局变量需用global哦