线程间的互斥锁

【一】互斥锁

【1】概述

        在Python中, 可以使用threading.lock类来实现线程间的互斥锁。互斥锁是一种同步原语, 用于保护共享资源, 确保在任意时刻只有一个线程可以访问共享资源。

        通俗的讲就是:设置一个全局变量让一个子进程或者子线程访问。通过锁机制来实现对共享资源的控制和保护。

import threading
​
# 全局变量
shared_flag = 155
​
# 创建互斥锁
lock = threading.Lock()
​
# 子线程函数
def inxian():
    global shared_flag
    # 获取互斥锁
    lock.acquire()
    try:
        # 对共享变量进行操作
        shared_flag += 1
​
    finally:
        # 释放互斥锁
        lock.release()
​
# 创建子线程并启动
t = threading.Thread(target=inxian())
t.start()
t.join()
​
# 打印最终结果
print(f'共享变量: {shared_flag}') # 156

import threading
​
mao = threading.Lock()
​
share_resourse = 5
​
def thread_func():
    global share_resourse
​
    mao.acquire()
​
    for i in range(564):
        share_resourse += 1
​
    mao.release()
​
threads = []
​
t = threading.Thread(target=thread_func)
threads.append(t)
t.start()
​
​
​
t.join()
​
print(share_resourse) #569

【2】主要作用

        互斥锁的主要作用是保护临界区,即一段对共享资源进行访问和修改的代码区域。当一个线程进入临界区时, 它会尝试获取互斥锁。如果互斥锁已经被其他线程持有,则当前线程会被阻塞,知道互斥锁被释放。这样可以确保只要一个线程能够执行临界区的代码,保证了共享资源的访问顺序和一致性。

【二】GIL锁

【1】(为什么有了GIL锁还要互斥锁)

        是一种CPython解释器中使用的机制, 它保证统一时刻只有一个线程执行Python字节码。 这个锁的存在是为了简化CPython 的实现,因为Cpython 在设计之初并不考虑多线程的执行。

注意:

        GIL(全局解释器)只存在于CPython解释器中,同时Python解释器的一个特定实现。其他编程语言和环境没有这个概念。

        GIL是保证解释器级别的数据的安全 。

        同一个进程下的多个线程无法利用多核优势!

        尽管GIL 确保了同一时刻只有一个线程执行Python 代码, 但是它并不能解决所有的线程安全问题。 因为在多线程环境下,即使只有一个线程在执行Python字节码,其他线程仍然可以在某个特定的时机获取GIL,并执行一些原声的操作(如I/O)或者调用C扩展模块,这些是不受GIL控制的

import threading
​
# 共享资源
shared_resource = 11
​
# 线程函数
def thread_function():
    global shared_resource
    # 访问共享资源
    for i in range(1000000):
        shared_resource += 1
​
# 创建多个线程并启动
threads = []
for i in range(4):
    t = threading.Thread(target=thread_function)
    i = i + shared_resource
    threads.append(t)
    t.start()
​
# 等待所有线程结束
for t in threads:
    t.join()
​
# 输出结果
print(shared_resource)
# GIL锁
​
import threading
​
# 定义共享资源
shared_resource = 0
​
# 创建一个锁对象
lock = threading.Lock()
​
# 定义任务函数
def task_function():
    global shared_resource
​
    # 获取锁
    lock.acquire()
​
    # 修改共享资源
    shared_resource += 1
​
    # 释放锁
    lock.release()
​
if __name__ == "__main__":
    # 创建线程列表
    threads = []
    for i in range(10):
        # 创建线程对象
        thread = threading.Thread(target=task_function)
        # 添加线程到列表
        threads.append(thread)
​
    # 启动所有线程
    for thread in threads:
        thread.start()
​
    # 等待所有线程执行完成
    for thread in threads:
        thread.join()
​
    # 打印共享资源的值
    print(f"共享资源的值为:{shared_resource}")
    # 共享资源的值为:10

GIL锁和互斥锁总结:

        GIL和互斥锁有不同的作用和使用场景。GIl保证了统一时刻只要一个线程执行Pyhon字节码。

  • 而互斥锁用于保护共享资源的访问和修改,避免数据竞争和不一致性问题。

  • 在多线程编程中,常常需要考虑;GIL和互斥锁来确保程序的正确性和性能。

  • GIL(全局解释器锁)和互斥锁主要用于线程同步,而不是进程同步

【三】信号量和事件

【1】前言 Semaphore信号量

        互斥锁 同时只允许一个线程更改数据, 而Semaphore是同时允许一定数量的线程更改数据。

就等于只有三台现货的iPhone15promax 先到先得。后面来买的人只能等。

  1. 信号量: 信号量是一种计数器,用于控制对共享资源的访问、他可以限制同时访问共享资源的进程数量。

  2. 事件: 事件是一种同步原语,用于实现进程间的通信和同步。一个事件有两种状态:已设置和未设置。 当一个进程等待一个事件时。

    未设置: 则该进程会被阻塞。

    已设置: 所有等待该事件的进程会被唤醒。

        事件通常用于线程/线程之间的通知和等待,例如:一个线程等待另一个线程完成某一个任务再继续执行。

        信号量Semaphore 和事件 它们两个都可以被进程跟线程使用

【四】 进程池跟线程池

【1】池是什么

  • 池就是用来保证计算机硬件安全的情况下最大限度的利用计算机。

  • 池降低了程序的运行效率,但是保证了计算机硬件的安全, 从而保证程序的正常运行

【2】语法

from concurrent.futures import ThreadPoolExecutor
​
# 默认开设当前计算机 cpu 个数五倍数的线程数
# 可以指定线程总数
pool = ThreadPoolExecutor(5)
  • 池子造出来后 里面固定存在五个线程

  • 这五个线程不会存在出现重复创建和销毁的过程

【3】进程池【Process Pool】

        进程池是一组预先创建的进程,这些可以并行地执行任务。

        通常情况下,进程池会创建一定数量的进程,并维护一个任务队列。当有新的任务需要执行时,会从队列中取出一个空闲的进程来执行任务,任务完成后,进程会返回进程池,等待下一个任务。

优点:

        进程池是能够充分利用多核处理器的优势,因为每个进程都运行在独立的地址空间中,彼此之间没有共享内存,所以不存在多线程中的竞态条件问题。然而,进程间的切换开销较大,因此在创建和销毁进程时会有一定的开销。

import multiprocessing
​
# 定义任务函数
def task_function(num):
    print(f"执行任务 {num}")
    result = num * 2
    return result
​
if __name__ == "__main__":
    # 创建进程池,指定进程数量为4
    pool = multiprocessing.Pool(processes=4)
​
    # 定义任务列表
    tasks = [1, 2, 3, 4, 5]
​
    # 使用进程池执行任务
    results = pool.map(task_function, tasks)
​
    # 关闭进程池
    pool.close()
​
    # 等待所有任务完成
    pool.join()
​
    # 打印结果
    print(results)

【4】线程池【Thread Pool】

进程池是一组预先创建的线程,这些可以并行地执行任务。

就跟进程一样。

# 线程池
import concurrent.futures
​
# 定义任务函数
def task_func(num):
    print(f'执行任务 {num}')
    result = num * 2
    return result
​
​
if __name__ == '__main__':
    # 创建线程池, 指定线程数量为4
    pool = concurrent.futures.ThreadPoolExecutor(max_workers=4)
    # 定义任务列表
    tasks = [1, 2, 3, 4, 5, 6, 7]
    # 使用进程池执行任务
    results = pool.map(task_func, tasks)
    # 关闭进程池
    pool.shutdown()
​
    print(list(results))
    # 执行任务1
    # 执行任务2
    # 执行任务3
    # 执行任务4
    # 执行任务5
    #
    # 执行任务6
    # 执行任务7
    # [2, 4, 6, 8, 10, 12, 14]

优点:

        线程池的优点是线程的创建和销毁开销相对较小,因为线程是在进程内部共享地址空间的, 它们之间可以直接访问共享内存。然而,由于多个线程共享同一份内存,存在竞态条件和线程安全问题,需要采取额外的同步机制(如锁);来保护共享数据。

【5】递归锁

递归锁的特点:

  • 可以被连续的acquire和release

  • 但是只能被第一个抢到这把锁执行上述操作

  • 它的内部有一个计数器 每acquire一次计数加一 每realse一次计数减一

  • 只要技术不为0 那么其他人无法抢到该锁

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值