互斥锁,线程技术

互斥锁

  • 本质

    将并发变成串行 虽然牺牲了程序的执行效率但是保证了数据安全
    
  • 作用

    多个程序同时操作一份数据的时候很容易产生数据错乱!!!
    为了避免数据错乱 我们需要使用互斥锁
    
  • 模拟抢票系统(合理)

    ###  db.json 自己提前创建好
     with open('db.json', 'w', encoding='utf-8') as f:
            dic={'count':1}
           json.dump(dic, f)
        
     
    ### searc方法 打印剩余票数
    def search():
        time.sleep(random.random())
        with open('db.json', encoding='utf-8') as f:
            dic = json.load(f)
            print(f'剩余票数:{dic["count"]}')
     
     
            
    ### 模拟多用户(多进程)抢票
    def get():
        with open('db.json', encoding='utf-8') as f:
            dic = json.load(f)
        time.sleep(random.randint(0, 2))
        if dic['count'] > 0:
            dic['count'] -= 1
            with open('db.json', 'w', encoding='utf-8') as f:
                json.dump(dic, f)
            print(f'用户:{os.getpid()} ,购买成功~~')
        else:
            print(f'{os.getpid()} 没票了~~~~')
     
     
    def task(lock):
        search()        
        lock.acquire() #给抢票购买, 加锁 , 既保证了数据的安全性,也保证了数据公平性
        get()
        lock.release()# 解锁
     
     
    if __name__ == '__main__':
        lock = Lock()
        for i in range(5):
            p1 = Process(target=task, args=(lock,)) # 模拟5个用户进程
            p1.start()
    
  • 关键字

    acquire: 加锁,所有人去抢,抢到的人进入,其他人等待解锁
    release: 解锁,释放

  • 强调

    互斥锁只应该出现在多个程序操作数据的地方 其他位置尽量不要加
    ps:以后我们自己处理锁的情况很少 只需要知道锁的功能即可

线程理论

  • 本质

    线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。

  • 进程与线程的理解

    进程是资源单位
    	进程相当于是车间 进程负责给内部的线程提供相应的资源()
    线程是执行单位
        线程相当于是车间里面的流水线 线程负责执行真正的功能
    
  • 特点

    1.线程是独立调度和分派的基本单位。
    2.同一进程中的多条线程将共享该进程中的全部系统资源。
    3.一个进程可以有很多线程,每条线程并行执行不同的任务。
    4.一个进程至少含有一个线程

  • 线程与进程的区别可以归纳为以下4点:

    1)地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的 线程在其它进程不可见。
    2)通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
    3)调度和切换:线程上下文切换比进程上下文切换要快得多。
    4)在多线程OS中,进程不是一个可执行的实体

  • 多进程与多线程的区别

    多进程
      需要申请内存空间 需要拷贝全部代码 资源消耗大
    多线程
      不需要申请内存空间 也不需要拷贝全部代码 资源消耗小
    

创建线程的两种方式

  • 强调

    开设线程不需要完整拷贝代码 所以无论什么系统都不会出现反复操作的情况
    也不需要在启动脚本中执行 但是为了兼容性和统一性 习惯在启动脚本中编写

  • 模块导入

    from threading import Thread
    
  • Python创建Thread对象语法如下:

    import threading
    threading.Thread(target=None, name=None,  args=())
    

    主要参数说明:

    1. target 是函数名字,需要调用的函数。
    2. name 设置线程名字。
    3. args 函数需要的参数,以元祖( tuple)的形式传入
  • Thread 对象主要方法说明:

    • run(): 用以表示线程活动的方法。
    • start():启动线程活动。
    • join(): 等待至线程中止。
    • isAlive(): 返回线程是否活动的
    • getName(): 返回线程名。
    • setName(): 设置线程名。
  • 方法一

    from threading import Thread
    import time
    
    def task(name):
        print(f'{name}正在运行')
        time.sleep(3)
        print(f'{name}运行结束')
    
    
    # t = Thread(target=task, args=('jason',))
    # t.start()
    # print('主线程')
    
    if __name__ == '__main__':  # 用不用main方法都行
        t = Thread(target=task, args=('jason',))
        t.start()
        print('主线程')
    
    '''
    输出结果:
    jason正在运行
    主线程
    jason运行结束
    '''
    
  • 方法二

    from threading import Thread
    import time
    
    
    class MyThread(Thread):
        def __init__(self, name):
            super().__init__()
            self.name = name
    
        def run(self):
            print(f'{self.name}正在运行')
            time.sleep(3)
            print(f'{self.name}运行结束')
    
    
    obj = MyThread('jason')
    obj.start()
    print('主线程')
    
    '''
    输出结果:
    jason正在运行
    主线程
    jason运行结束
    '''
    

join方法

from threading import Thread
import time


def task(name):
    print(f'{name}正在运行')
    time.sleep(3)
    print(f'{name}运行结束')


t = Thread(target=task, args=('jason', ))
t.start()
t.join()  # 主线程代码等待子线程代码运行完毕之后再往下执行,和进程的join方法作用一致
print('主线程')

'''
输出结果:
jason正在运行
jason运行结束
主线程
'''

同一个进程下线程间数据共享

from threading import Thread

money = 1000


def func():
    global money
    money = 666


t = Thread(target=func)
t.start()
t.join()  # 确保线程运行完毕 再查找money 结果更具有说服性
print(money)

'''
输出结果:
666
'''

线程对象相关方法

Python的threading模块有个current_thread()函数,它将返回当前线程的示例。从当前线程的示例可以获得前运行线程名字。

from threading import Thread, current_thread
current_thread().name

强调:同一个进程下开设的多个线程拥有相同的进程号

统计进程下的线程数:active_count()

守护线程

  • 本质

    在多线程开发中,如果子线程设定为了守护线程,守护线程会等待主线程运行完毕后被销毁。一个主线程可以设置多个守护线程,守护线程运行的前提是,主线程必须存在,如果主线程不存在了,守护线程会被销毁。

  • 代码实操

    from threading import Thread
    import time
    
    def task():
        print('子线程运行task函数')
        time.sleep(3)
        print('子线程运行task结束')
    
    
    t = Thread(target=task)
    t.daemon = True
    t.start()
    # t.daemon = True  # 会报错
    print('主线程')
    
    '''
    输出结果:
    子线程运行task函数
    主线程
    '''
    

强调:进程下所有的非守护线程结束 主线程(主进程)才能真正结束!!!

GIL全局解释器锁

  • 什么是 GIL 呢?

    GIL 是最流程的 CPython 解释器(平常称为 Python)中的一个技术术语,中文译为全局解释器锁,其本质上类似操作系统的 Mutex。
    
  • GIL 的功能

    在 CPython 解释器中执行的每一个 Python 线程,都会先锁住自己,以阻止别的线程执行。
    
  • 储备知识

    python解释器也是由编程语言写出来的

    Cpython 用C写出来的
    Jpython 用Java写出来的
    Pypython 用python写出来的

    ps:最常用的就是Cpython(默认)

  • 官方文档

    In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)

    翻译:

    在 CPython 解释器中,全局解释锁 GIL 是在于执行 Python 字节码时,为了保护访问 Python 对象 而阻止多个线程执行的一把互斥锁。这把锁的存在主要是因为 CPython 解释器的内存管理不是线程安全的。然而直到今天 GIL 依旧存在,现在的很多功能已经习惯于依赖它作为执行的保证。

    总结:

    1.GIL的研究是Cpython解释器的特点 不是python语言的特点
    2.GIL本质也是一把互斥锁
    3.GIL的存在使得同一个进程下的多个线程无法同时执行(关键)
    言外之意:单进程下的多线程无法利用多核优势 效率低!!!
    4.GIL的存在主要是因为cpython解释器中垃圾回收机制不是线程安全的

    GIL 工作流程示意图:
    GIL 工作流程示意图

  • 问题引出:

    1.误解:python的多线程就是垃圾 利用不到多核优势

    python的多线程确实无法使用多核优势 但是在IO密集型的任务下是有用的
    

    2.误解:既然有GIL 那么以后我们写代码都不需要加互斥锁

    不对 GIL只确保解释器层面数据不会错乱(垃圾回收机制)
    针对程序中自己的数据应该自己加锁处理
    

总结:

以后用python就可以多进程下面开设多线程从而达到效率最大化

1.所有的解释型语言都无法做到同一个进程下多个线程利用多核优势

2.GIL在实际编程中其实不用考虑

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值