【一】互斥锁
【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 先到先得。后面来买的人只能等。
-
信号量: 信号量是一种计数器,用于控制对共享资源的访问、他可以限制同时访问共享资源的进程数量。
-
事件: 事件是一种同步原语,用于实现进程间的通信和同步。一个事件有两种状态:已设置和未设置。 当一个进程等待一个事件时。
未设置: 则该进程会被阻塞。
已设置: 所有等待该事件的进程会被唤醒。
事件通常用于线程/线程之间的通知和等待,例如:一个线程等待另一个线程完成某一个任务再继续执行。
信号量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 那么其他人无法抢到该锁