Python 多线程 threading

线程是CPU分配资源的基本单位,也就是说线程是程序执行的最小单位。

主线程:

当一个程序开始运行,这个程序就变成了一个进程,同时也会开启一个线程,这个线程就是程序的主线程。一个进程相当于一个或者多个线程。

当没有多线程编程时,一个进程相当于一个主线程;当有多线程编程时,一个进程包含多个线程(含主线程)。

子线程:

一个子线程可以看做是程序执⾏的⼀条分⽀,⼦线程启动后会和主线程同时执⾏。

1.单线程和多线程

线程是程序执行的最小单位,如果有多个任务要执行,单线程只能一个任务执行完再执行下一个任务,而多线程可以同时执行多个任务。

Python中可以使用内置的 threading 模块实现多线程。

(1)单线程

import time

def func1():
    for i in range(3):
        time.sleep(1)
        print("函数1--%s" % i)

def func2(name):
    for i in range(3):
        time.sleep(1)
        print("函数2,你好,%s --%s" % (name, i))

func1()
func2("sam")

# 程序按顺序执行,func1执行完之后才会执行func2
# 函数1--0
# 函数1--1
# 函数1--2
# 函数2,你好,sam --0
# 函数2,你好,sam --1
# 函数2,你好,sam --2

(2)多线程

# -*- coding: utf-8 -*-
import threading, time

def func1():
    for i in range(3):
        time.sleep(1)
        print("函数1--%s" % i)

def func2(name):
    for i in range(3):
        time.sleep(1)
        print("函数2,你好,%s --%s" % (name, i))

if __name__ == '__main__':

    # 创建1个子线程,传入函数名作为参数
    t1 = threading.Thread(target=func1)
    # 创建1个子线程,如果传入的函数带有参数,参数以元组的形式传入
    t2 = threading.Thread(target=func2, args=("sam",))
    # 启动线程
    t1.start()
    t2.start()
    # 子线程启动后会和主线程同时执行。
    for i in range(3):
        time.sleep(1)
        print("主线程")

# 由于cpu调度不同,多次运行的输出结果可能不一样,本次输出结果如下:
# 函数2,你好,sam --0函数1--0
# 
# 主线程
# 函数2,你好,sam --1主线程函数1--1
# 
# 
# 函数2,你好,sam --2函数1--2
# 
# 主线程

2.通过继承的方式实现多线程

继承 Thread 类,重新run()方法也可以实现多进程。

# -*- coding: utf-8 -*-
import threading, time


class Thread_Func1(threading.Thread):
    def run(self):
        for i in range(1, 4):
            time.sleep(1)
            print(f"执行子线程1---{i}")

class Thread_Func2(threading.Thread):
    def run(self):
        for i in range(1, 4):
            time.sleep(1)
            print(f"执行子线程2---{i}")

if __name__ == '__main__':
    t1 = Thread_Func1()
    t2 = Thread_Func2()
    t1.start()
    t2.start()

# 多次运行的输出结果可能不一样,本次结果如下:
# 执行子线程2---1执行子线程1---1
#
# 执行子线程2---2
# 执行子线程1---2
# 执行子线程2---3
# 执行子线程1---3

3.线程的常用方法

  • threading.current_thread() : 获取当前线程对象
  • threading.enumerate(): 获取当前的所有线程信息
  • 线程对象.name: 获取线程名称,赋值可修改线程名称

(1)获取当前线程对象

import threading, time

class Thread_Func1(threading.Thread):
    def run(self):
        print(threading.current_thread())   # <Thread_Func1(Thread-1, started 54484)>
        for i in range(1, 4):
            time.sleep(1)
            print(f"执行子线程1---{i}")

class Thread_Func2(threading.Thread):
    def run(self):
        print(threading.current_thread())   # <Thread_Func2(Thread-2, started 42216)>
        for i in range(1, 4):
            time.sleep(1)
            print(f"执行子线程2---{i}")

if __name__ == '__main__':
    t1 = Thread_Func1()
    t2 = Thread_Func2()
    print(threading.current_thread())   # <_MainThread(MainThread, started 26196)>
    t1.start()
    t2.start()

(2)获取线程信息

import threading, time

class Thread_Func1(threading.Thread):
    def run(self):
        for i in range(1, 4):
            time.sleep(1)
            print(f"执行子线程1---{i}")

class Thread_Func2(threading.Thread):
    def run(self):
        for i in range(1, 4):
            time.sleep(1)
            print(f"执行子线程2---{i}")

if __name__ == '__main__':
    t1 = Thread_Func1()
    t2 = Thread_Func2()
    print(threading.enumerate())    # 此时子线程未启动,只有主线程
    # <_MainThread(MainThread, started 26196)>
    t1.start()
    t2.start()
    print(threading.enumerate())    # 启动了2个子线程,线程信息如下
    # [<_MainThread(MainThread, started 5224)>, <Thread_Func1(Thread-1, started 44416)>, <Thread_Func2(Thread-2, started 52096)>]

(3)获取和修改线程名

import threading, time

class Thread_Func1(threading.Thread):
    def run(self):
        thread = threading.current_thread()
        print(thread.name)  # Thread-1
        thread.name = "子线程" # 修改当前线程名
        print(thread.name)  # 子线程
        for i in range(1, 4):
            time.sleep(1)
            print(f"执行子线程1---{i}")


if __name__ == '__main__':
    thread_main = threading.current_thread()
    t1 = Thread_Func1()
    print(thread_main.name) # MainThread
    t1.start()
    print(threading.enumerate())

4.线程共享全局变量时的资源竞争

多个线程共享全局变量时,可能会发生多个线程同时对一个全局变量进行操作的情况。

import threading

num = 100

def func1():
    global num
    for i in range(100):
        if num >= 0:
            print(f"func1: num={num}")
        num -= 1

def func2():
    global num
    for i in range(100):
        if num >= 0:
            print(f"func2: num={num}")
        num -= 1

if __name__ == '__main__':
    t1 = threading.Thread(target=func1)
    t2 = threading.Thread(target=func2)
    t1.start()
    t2.start()

上述代码运行时可能会出现如下结果:

 

 如图所示,2个线程同时访问了全局变量num,所以都输出变量值为66。

5.锁

当多个线程同时对1个全局变量进行操作时,可能会发生数据不准确的问题。

比如有全局变量num = 10,线程1将num值加5再赋值给num,线程2将num值加10再赋值给num,我们希望的执行顺序如下:

  • 1. 线程1 读取到num为10,将num加5,完成后num=15
  • 2. 线程2 读取到num为15,将num加10,完成后 num=25

由于多线程可能出现的同时操作全局变量问题,程序执行可能如下:

  • 1. 线程1和线程2同时读取到num为10
  • 2. 线程1将num加5再赋值给num,num应该为15,
  • 3. 由于线程2和线程1同时读取的num值,线程1对num做了修改,但线程2还不知道,操作进行到给num值加10,再赋值给num,最终num值为20

为了解决多线程共享资源可能导致的数据不准确的问题,我们可以给各线程相关操作加上锁。

线程1线程2都要对一个全局变量num进行操作,线程1对num操作前加上锁,操作完再释放锁,同样,线程2对num操作前也加上同一个锁,操作完再释放锁。

线程1和线程2执行到加锁的代码时,假如线程1先获得了锁,那么线程1会一直执行,直到释放锁之后,线程2才会再次和线程1争夺锁,谁抢到了锁谁才能继续执行加锁部分的代码。

也就是说,多个线程都加锁,谁先获得锁,谁就能一直执行到释放锁,在释放锁之前,其他加锁的进程不会执行。

示例1:

import threading

lock = threading.Lock()
num = 100

def func1():
    global num
    lock.acquire()  # 循环执行前加锁
    for i in range(100):
        if num > 0:
            print(f"func1: num={num}")
        num -= 1
    lock.release()  # 循环之后完成后才释放锁

def func2():
    global num
    for i in range(100):
        lock.acquire()    # 每次循环就加锁
        if num > 0:
            print(f"func2: num={num}")
        num -= 1
        lock.release()    # 一次循环结束就释放锁

if __name__ == '__main__':
    t1 = threading.Thread(target=func1)
    t2 = threading.Thread(target=func2)
    t1.start()
    t2.start()

上述代码,func1在循环开始前加了锁,在循环结束后才释放锁,所以上述代码会在func1都执行完后才会执行func2,也就是会一直输出func1的打印内容,func1执行完后,num = 0,func2执行没有打印输出内容。

注意,一定要是同一个锁。上述代码,如果func1和func2使用不同的锁对象加锁,那么相当于没加锁。

示例2:

import threading

lock = threading.Lock()
num = 100

def func1():
    global num
    for i in range(100):
        lock.acquire() # 每次循环加锁
        if num > 0:
            print(f"func1: num={num}")
        num -= 1
        lock.release()  # 一次循环结束就释放锁

def func2():
    global num
    for i in range(100):
        lock.acquire()
        if num > 0:
            print(f"func2: num={num}")
        num -= 1
        lock.release()

if __name__ == '__main__':
    t1 = threading.Thread(target=func1)
    t2 = threading.Thread(target=func2)
    t1.start()
    t2.start()

上述代码,2个子进程都是在每次循环开始才加锁,1次循环结束就释放锁,所以是多num交替修改(可能1个线程连续修改多次,然后另一个线程才获取到锁)。

 

posted on 2023-04-14 12:28  木去  阅读(67)  评论(0)    收藏  举报