Python多线程入门学习笔记

1 概念

1.1 线程

线程(Thread)也叫轻量级进程,是操作系统能够进行运算调度的最小单位,它被包涵在进程之中,是进程中的实际运作单位。线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。

每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

每个线程都有他自己的一组CPU寄存器,称为线程的上下文,该上下文反映了线程上次运行该线程的CPU寄存器的状态。

指令指针和堆栈指针寄存器是线程上下文中两个最重要的寄存器,线程总是在进程得到上下文中运行的,这些地址都用于标志拥有线程的进程地址空间中的内存。

  • 线程可以被抢占(中断)。
  • 在其他线程正在运行时,线程可以暂时搁置(也称为睡眠) – 这就是线程的退让。

线程可以分为:

  • **内核线程:**由操作系统内核创建和撤销。
  • **用户线程:**不需要内核支持而在用户程序中实现的线程。

1.2 多线程

线程在程序中是独立的、并发的执行流。与分隔的进程相比,进程中线程之间的隔离程度要小,它们共享内存、文件句柄和其他进程应有的状态。

因为线程的划分尺度小于进程,使得多线程程序的并发性高。进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。

线程比进程具有更高的性能,这是由于同一个进程中的多个线程共享同一个进程的虚拟空间。线程共享的环境包括进程代码段、进程的公有数据等,利用这些共享的数据,线程之间很容易实现通信。

操作系统在创建进程时,必须为该进程分配独立的内存空间,并分配大量的相关资源,但创建线程则简单得多。因此,使用多线程来实现并发比使用多进程的性能要高得多。

总结起来,使用多线程编程具有如下几个优点:

  • 进程之间不能共享内存,但线程之间共享内存非常容易。
  • 操作系统在创建进程时,需要为该进程重新分配系统资源,但创建线程的代价则小得多。因此,使用多线程来实现多任务并发执行比使用多进程的效率高。
  • Python 语言内置了多线程功能支持,而不是单纯地作为底层操作系统的调度方式,从而简化了 Python 的多线程编程。

2 线程实现

Python3 线程中常用的两个模块为:

  • _thread
  • threading(推荐使用)

thread 模块已被废弃。用户可以使用 threading 模块代替。所以,在 Python3 中不能再使用"thread" 模块。为了兼容性,Python3 将 thread 重命名为 “_thread”。

2.1 threading

threading模块创建线程有两种方式:

  • 从 threading.Thread 继承创建一个新的子类,并实例化后调用 start() 方法启动新线程
  • 直接通过threading.Thread创建一个线程
import threading
import time


def run(name):
    print("task", name)
    time.sleep(1)
    print(name, "1s")


class MyThread(threading.Thread):
    def __init__(self, name):
        super(MyThread, self).__init__()
        self.name = name

    def run(self):
        print("task", self.name)
        time.sleep(1)
        print(self.name, "1s")


def threading_thread():
    """
    普通创建方式
    :return:
    """
    t1 = threading.Thread(target=run, args=("t1",))
    t2 = threading.Thread(target=run, args=("t2",))
    t1.start()
    t2.start()


def myself_thread():
    """
    自定义线程
    :return:
    """
    t1 = MyThread("t3")
    t2 = MyThread("t4")
    t1.start()
    t2.start()


if __name__ == "__main__":
    threading_thread()
    myself_thread()

2.2 守护进程

在python中,线程通过setDaemon(True|False)来设置是否为守护线程。如果你设置一个线程为守护线程,就表示你在说这个线程是不重要的,在进程退出的时候,不用等待这个线程退出。如果你的主线程在退出的时候,不用等待那些子线程完成,那就设置这些线程的daemon属性。即在线程开始(thread.start())之前,调用setDeamon()函数,设定线程的daemon标志。(thread.setDaemon(True))就表示这个线程“不重要”。默认情况下,新线程通常会生成非守护线程或普通线程,如果新线程在运行,主线程将永远等待,无法正常退出

守护线程的作用:
守护线程作用是为其他线程提供便利服务,守护线程最典型的应用就是 GC (垃圾收集器)。

import threading
import time


def run(name):
    print("task")
    time.sleep(1)
    print(name)


def daemon_thread():
    """
    守护进程——当主线程结束时,子线程也将立即结束,不再执行
    :return:
    """
    t = threading.Thread(target=run, args=("t1",))
    t.setDaemon(True)  # 把子进程设置为守护线程,必须在start()之前设置
    t.start()


def daemon__thread():
    """
    守护进程——守护线程执行结束之后,主线程再结束
    :return:
    """
    t = threading.Thread(target=run, args=("t1",))
    t.setDaemon(True)
    t.start()
    t.join()


if __name__ == "__main__":
    print("start")
    # daemon_thread()
    daemon__thread()

2.3 多线程共享全局变量

import threading
import time

g_num = 100


def work1():
    global g_num
    for i in range(3):
        g_num += 1
    print(f"in work1 g_num is :{g_num}")


def work2():
    global g_num
    print(f"in work2 g_num is :{g_num}")


if __name__ == '__main__':
    t1 = threading.Thread(target=work1)
    t1.start()
    time.sleep(1)
    t2 = threading.Thread(target=work2)
    t2.start()

2.4 锁

2.4.1 全局解释器锁(GIL)

在这里插入图片描述

GIL保护的是解释器级的数据,保护用户自己的数据则需要自己枷锁处理,如下图 :
 在这里插入图片描述

2.4.2 互斥锁

GIL和Lock是两把锁,保护的数据不一样,前者是解释器级别的(保护的是解释器级别的数据,比如垃圾回收的数据),后者是保护用户自己开发的应用程序的数据,

互斥锁保证同一时间只有一个线程修改数据。加了互斥锁,线程就变成串行了。

import time
from threading import Thread, Lock, RLock


def work(name, lock):
    """
    互斥锁
    """
    global n
    lock.acquire()
    temp = n
    n = temp - 1
    print(name, ":", n)
    lock.release()



if __name__ == '__main__':
    n = 100

    # 互斥锁
    lock = Lock()
    p_l = []
    for i in range(10):
        p = Thread(target=work, args=(f'p{i}', lock,))
        p_l.append(p)
        p.start()
    for p in p_l:
        p.join()

2.4.3 递归锁

递归锁对象允许多次acquire和多次release。

在Python中为了支持在同一线程中多次请求同一资源,python提供了递归锁RLock。

RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。

直到一个线程所有的acquire都被release,其他的线程才能获得资源。

class Account:
    def __init__(self, account, balance):
        self.account = account
        self._balance = balance
        self.lock = RLock()

    def get_balance(self):
        return self._balance

    def draw(self, draw_num):
        self.lock.acquire()
        try:
            if self._balance < draw_num:
                print("取钱失败,余额不足")
            else:
                print(f"成功取出{draw_num}")
                self._balance -= draw_num
                print(f"余额为{self._balance}\n")
        finally:
            self.lock.release()


def draw(account: Account, num: int):
    account.draw(num)


if __name__ == '__main__':
    account = Account("xou", 200)
    d1 = Thread(target=draw, args=(account, 100,))
    d2 = Thread(target=draw, args=(account, 150,))
    d1.start()
    d2.start()

2.5 信号量

import threading
import time


def run(n, lock):
    lock.acquire()  # 加锁
    print(f"run the thread:{n}\n")
    time.sleep(1)
    lock.release()  # 释放


if __name__ == '__main__':
    num = 0
    semaphore = threading.BoundedSemaphore(5)  # 最多允许5个线程同时运行
    for i in range(22):
        t = threading.Thread(target=run, args=(f"t-{i}", semaphore))
        t.start()

2.6 Event(事件对象)

方法:
is_set()
当且仅当内部标志为True时返回True

set()
将内部标志设置为True。所有等待它成为True的线程都被唤醒。当标志保持在True的状态时,线程调用wait()是不会阻塞的。

clear()
将内部标志重置为False。随后,调用wait()的线程将阻塞,直到另一个线程调用set()将内部标志重新设置为True

wait(timeout=None)
阻塞直到内部标志为真。如果内部标志在wait()方法调用时为True,则立即返回。否则,则阻塞,直到另一个线程调用set()将标志设置为True,或发生超时。
该方法总是返回True,除非设置了timeout并发生超时。

# 利用Event类模拟红绿灯
import threading
import time

event = threading.Event()


def lighter():
    count = 1
    event.set()  # 初始值为绿灯
    while True:
        if 5 < count <= 10:
            event.clear()  # 红灯,清除标志位
            print(f"{count}now is red")
        elif count > 10:
            event.set()  # 绿灯,设置标志位
            count = 0
        else:
            print(f"{count}now is green")

        time.sleep(1)
        count += 1


def car(name):
    while True:
        if event.is_set():  # 判断是否设置了标志位
            print("[%s] running..." % name)
            time.sleep(1)
        else:
            print("[%s] sees red light,waiting..." % name)
            event.wait()
            print("[%s] green light is on,start going..." % name)


if __name__ == '__main__':
    light = threading.Thread(target=lighter, )
    light.start()

    car = threading.Thread(target=car, args=("MINI",))
    car.start()
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值