Day 35 守护线程,互斥锁

Day 02

为什么要有线程

进程的缺点

  • 进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。
  • 进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。

生产者消费者模型

​ 生产者消费者模式是通过·一个容器来解决生产者和消费者的强耦合问题

​ 生产者和消费者彼此之间不能直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取

​ 阻塞队列就是一个缓冲区,平衡了生产者和消费者的处理能力。

线程理论

​ 线程理论

  • 计算机相当于大工厂,工厂里有一个个车间(进程),有很多人(线程)干不同的事
  • 真正干活的是线程----------线程是CPU调度的最小单位
  • 进程是资源分配的最小单位,线程是CPU调度的最小单位。每一个进程至少有一个线程
  • 线程开销更小,更轻量级

开启线程的两种方式

创建线程的两种方式和进程是一样的

函数方法

from threading import Thread
from time import sleep
from random import randint


# 函数
def task(n):
    print(f"我是线程{n}---------启动!")
    sleep(randint(1, 4))
    print(f"我是线程{n}---------结束!")

    
if __name__ == '__main__':
    for i in range(1, 4):
        t1 = Thread(target=task, args=(i, ))
        t1.start()
    print("主线程结束!")

类方法

class MyTask(Thread):
    def __init__(self, n):
        super().__init__()
        self.n = n

    def run(self) -> None:
        print(f"我是线程{self.n}---------启动!")
        sleep(randint(1, 4))
        print(f"我是线程{self.n}---------结束!")


if __name__ == '__main__':
    for i in range(1, 4):
        t1 = MyTask(i)
        t1.start()
    print("主线程结束!")

TCP服务端实现并发效果

线程对象join方法

该函数为阻塞函数,会阻塞知道等待队列中所有的数据被处理完毕

if __name__ == '__main__':
    for i in range(1, 4):
        t1 = MyTask(i)
        t1.start()
    t1.join()
    print("主线程结束!")
----------------------------------
我是线程1---------启动!
我是线程2---------启动!
我是线程3---------启动!
我是线程3---------结束!
主线程结束!  # 线程3结束开始执行主进行 但是不影响其他线程的正常运行
我是线程1---------结束!
我是线程2---------结束!

同一个线程下的多个线程数据共享

一个线程修改后另外的数据只能在修改后的基础上修改

class MyTask(Thread):

    def __init__(self, n):
        super().__init__()
        self.n = n

    def run(self) -> None:
        global number
        print(f"我是线程{self.n}---------启动!")
        sleep(1)
        number -= 1
        print(f"我是线程{self.n}---------{number}!")

线程对象及其方法

active_count():返回活跃的线程数量,(主+子)

current_thread():

返回当前的Thread对象,该对象对应于调用者的控制线程。如果未通过线程模块创建调用者的控制线程,则将返回功能受限的虚拟线程对象。可以通过 .属性 或方法获取 线程的属性方法

Thread实例对象的方法

  • is_alive():返回线程对象是否存活True or False
  • getName():返回线程名字
  • getName():设置线程名字
  • isDaemon():返回是不是守护线程 True or False

守护线程

无论是进程还是线程,都遵循:守护xx会等待主xx运行完毕后被销毁。需要强调的是:运行完毕并非终止运行。

  1. 对主进程来说,运行完毕指的是主进程代码运行完毕
  2. 对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕
if __name__ == '__main__':
    t1 = MyTask(1, 2)
    t2 = MyTask(2, 3)
    # t1.daemon = True  # t1只睡了两秒,主线程还要等到睡了三秒的t2完成,所以t1会继续运行
    t2.daemon = True  # 由于t1睡的时间短,所以t1结束,主线程就只等待t2结束,而t2是守护线程,所以t1结束t2也就结束了
    t1.start()
    t2.start()

    print("主线程结束!")
-----------------------------------
>>> 我是线程1---------启动!
>>> 我是线程2---------启动!
>>> 主线程结束!
>>> 我是线程1---------结束!

线程互斥锁

线程Lock和进程Lock是两个完全不同的对象

Lock互斥锁

原语:即原子语句,这个过程不能被中断。

同上数据共享栗子

不加锁 用文件读取的模式更加明显

from threading import Thread, Lock
from time import sleep
from random import randint

number = 1000000


class MyTask(Thread):

    def __init__(self, n):
        super().__init__()
        self.n = n

    def run(self) -> None:
        global number
        sleep(1)
        # mutex.acquire()
        for i in range(1, 100001): # 每个数据减去 10w
            number -= 1
        # mutex.release()


if __name__ == '__main__':
    mutex = Lock()
    for i in range(1, 3):  # 开启两个线程
        t = MyTask(i)
        t.start()

sleep(10)
print(number)
----------------------------------
>>> 819193

所以为了防止这样的数据错乱我们需要给他加一把锁

    def run(self) -> None:
        global number
        sleep(1)
        mutex.acquire()
        for i in range(1, 100001):
            number -= 1
        mutex.release()


if __name__ == '__main__':
    mutex = Lock()
    for i in range(1, 3):
        t = MyTask(i)
        t.start()

sleep(20)  # 防止线程还没结束 就直接打印结果不准确
print(number)
------------------------------
>>> 800000

GIL全局解释所理论

#1 python的解释器有很多,cpython,jpython,pypy(python写的解释器)
#2 python的库多,库都是基于cpython写起来的,其他解释器没有那么多的库
#3 cpython中有一个全局大锁,每条线程要执行,必须获取到这个锁
#4  为什么会有这个锁呢?python的垃圾回收机制
#5 python的多线程其实就是单线程
#6 某个线程想要执行,必须先拿到GIL,我们可以把GIL看作是“通行证”,并且在一个python进程中,GIL只有一个。拿不到通行证的线程,就不允许进入CPU执行

# 7 总结:cpython解释器中有一个全局锁(GIL),线程必须获取到GIL才能执行,我们开的多线程,不管有几个cpu,同一时刻,只有一个线程在执行(python的多线程,不能利用多核优势)

# 8 如果是io密集型操作:开多线程
# 9如果是计算密集型:开多进程
以上两句话,只针对与cpython解释器
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值