Python高级——多线程(threading)

线程

  线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。
————目录————:

  • 多线程的基本使用
  • 查看线程名称及数量
  • 线程的参数传递
  • 线程的执行顺序
  • 守护线程
  • 线程的全局变量
  • 互斥锁
  • 死锁
  • 自定义线程

1.多线程的基本使用
(1)导入threading模块

import threading

(2)创建threading.Thread实例对象

对象名 = threading.Thread(target=函数名, args=参数)

参数的传递方式一般为元组或字典,下面会详细解释
(3)线程启动

对象名.start()

例:

# 1.导入模块
import threading
import time


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


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


if __name__ == '__main__':
    print('---开始---')
    # 2.创建threading.Thread实例对象
    t1 = threading.Thread(target=sing)
    t2 = threading.Thread(target=dance)
    # 3.线程启动
    t1.start()
    t2.start()
    print('---结束---')

这里写图片描述
小结:
(1)使用多线程并发的操作,花费时间要短很多
(2)当调用start()时,才会真正的创建线程,并且开始执行
(3)主线程:mainThread,Main函数或者程序主入口,都可以称为主线程
(4)子线程:Thread-x 使用 threading.Thread() 创建出来的都是子线程
(5)主线程会在子线程运行结束后结束

2.查看线程名称及数量

方法 描述
threading.current_thread 返回当前线程名称
thread_list = threading.enumerate() 获取所有正在活跃的线程,返回值是一个列表

len(thread_list):活跃线程列表的长度,即线程数量

小结:
(1)每个线程都有一个唯一标示符,来区分线程中的主次关系
(2)每个线程默认有一个名字,尽管上面的例子中没有指定线程对象的name,但是python会自动为线程指定一个名字。
(3)线程数量:主线程数 + 子线程数

3.线程的参数传递
(1)元祖传参:args = (参数1,参数2,…)
注意:如果只有一个参数,应写为args=(参数,),逗号不可省略,证明他是一个元组
(2)字典传参:kwargs = {“a”: 10, “b”:100,….}
(3)混合使用:args = (参数1,), kwargs = {“b”:100,…….}

4.线程的执行顺序
  多线程程序的执行顺序是不确定的。当执行到sleep语句时,线程将被阻塞(Blocked),到sleep结束后,线程进入就绪(Runnable)状态,等待调度。而线程调度将自行选择一个线程执行。可通过time.sleep()方式来调节线程的执行顺序。
  
5.守护线程
  如果在程序中将子线程设置为守护线程,则该子线程会在主线程结束时自动退出,设置方式为thread.setDaemon(True),要在thread.start()之前设置,默认是false的,也就是主线程结束时,子线程依然在执行。
设置守护线程:

线程名.setDaemon(True)

设置守护线程后,主线程结束,子线程也结束。
例:

import time
import threading


def work():
    for i in range(10):
        print("正在执行work...", i)
        time.sleep(0.5)


if __name__ == '__main__':
    # 创建线程对象
    t1 = threading.Thread(target=work)
    # 让子线程守护主线程
    # setDaemon() 设置子线程守护主线程,当主线程结束的时候,子线程也要结束
    t1.setDaemon(True)
    # 启动子线程
    t1.start()
    # 主线程睡眠 2秒
    time.sleep(2)
    print("Game Over")
    # 程序要退出
    exit()

这里写图片描述
6.线程的全局变量
  线程间可以共享全局变量
例:

import threading
import time
n = 0


def fun1():
    global n
    n += 1
    print("fun1:n=%d" % n)


def fun2():
    global n
    n += 1
    print("fun2:n=%d" % n)


if __name__ == '__main__':
    f1 = threading.Thread(target=fun1)
    f2 = threading.Thread(target=fun2)
    f1.start()
    f2.start()
    time.sleep(1)
    print(n)

这里写图片描述
7.互斥锁
异步方式:多任务间执行不分先后顺序,可同时进行,有多个主线。
同步方式:多任务间执行有先后顺序,不可同时进行,只有一个主线。

  当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制,否则会导致错误,此时需要使用互斥锁,修改数据时现将资源锁定,修改后再解锁资源。
  
用法:
创建:lock = threading.Lock()
锁定:lock.acquire()
解锁:lock.release()
注意:如果锁定时当前资源已锁定,会进入阻塞状态,直到这个锁被解锁。
例:
异步方式修改共享资源:

import threading
import time
n = 0


def fun1():
    global n
    for i in range(1000000):
        n += 1
    print("fun1:n=%d" % n)


def fun2():
    global n
    for i in range(1000000):
        n += 1
    print("fun2:n=%d" % n)


if __name__ == '__main__':
    f1 = threading.Thread(target=fun1)
    f2 = threading.Thread(target=fun2)
    f1.start()
    f2.start()
    time.sleep(2)
    print(n)

执行结果:
这里写图片描述
此时会发现,结果并不是预期的n=2000000,应为异步方式同时修改某个共享资源可能会发生错误,此时需要给资源上锁。

同步方式修改共享资源:

import threading
import time
n = 0


def fun1():
    global n
    for i in range(1000000):
        lock.acquire()
        n += 1
        lock.release()
    print("fun1:n=%d" % n)


def fun2():
    global n
    for i in range(1000000):
        lock.acquire()
        n += 1
        lock.release()
    print("fun2:n=%d" % n)


if __name__ == '__main__':
    f1 = threading.Thread(target=fun1)
    f2 = threading.Thread(target=fun2)
    lock = threading.Lock()
    f1.start()
    f2.start()
    time.sleep(2)
    print(n)

执行结果:
这里写图片描述
可以看出,给资源上锁进行同步控制后,可以使多线程正确的修改共享数据。

8.死锁:
  在线程间共享多个资源时,有两个线程分别占有一部分资源并且同时等待对 方的资源,会造成死锁,造成程序停止响应。所以设计程序时应尽量避免死锁的发生。
死锁的一个简单例子:

import threading
import time
lock1 = threading.Lock()  # 创建一个互斥锁
lock2 = threading.Lock()  # 创建一个互斥锁


def fun1():
    lock1.acquire()
    print("fun1 lock1锁住")
    # 线程停止0.5s,等待另一个锁上锁
    time.sleep(0.5)
    lock2.acquire()
    print("fun1 lock2锁住")
    lock1.release()
    lock2.release()


def fun2():
    lock2.acquire()
    print("fun2 lock2锁住")
    # 线程停止0.5s,等待另一个锁上锁
    time.sleep(0.5)
    lock1.acquire()
    print("fun2 lock1锁住")
    lock2.release()
    lock1.release()


if __name__ == '__main__':
    t1 = threading.Thread(target=fun1)  # 创建一个线程对象
    t2 = threading.Thread(target=fun2)  # 创建一个线程对象
    t1.start()
    t2.start()

程序会卡死,无法进行下一步:
这里写图片描述
9.自定义线程
  通过使用threading模块能完成多任务的程序开发,为了让每个线程的封装性更完美,所以使用threading模块时,往往会定义一个新的子类class,只需要继承threading.Thread,重写某些方法。
例:

import threading
import time

"""
    自定义线程类:
    1)定义一个类,让该类继承 threading.Thread
    2) 重写父类的 run() 方法
    3)使用线程类,创建线程对象
    4)通过线程对象的start() 方法,启动线程

    class MyThread():

        def run(self):
            ...

        def start(self):
            self.run()

"""


# MyThread 就是一个线程类
class MyThread(threading.Thread):

    def __init__(self, num):
        # 调用当前类的父类的构造方法
        super().__init__()
        # 把 num 保存到实例属性
        self.num = num

    # 重写 父类的run方法
    def run(self):
        for i in range(self.num):
            # self.name 父类属性,保存的是当前线程的名称
            print("Mythread-run 正在运行", i, self.name)
            time.sleep(0.5)
    # 继承了父类的 start()
    # def start(self):
    #     self.run()


if __name__ == '__main__':
    # 实例化对象
    mythread = MyThread(5)
    # 启动线程
    mythread.start()

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

猜你喜欢

转载自blog.csdn.net/zsh142537/article/details/82422735