iOS常见锁及应用(笔记版)

news2024/9/23 13:23:01

什么是锁?

在程序中,当多个任务(或线程)同时访问同一个资源时,比如多个操作同时修改一份数据,可能会导致数据不一致。这时候,我们需要“锁”来确保同一时间只有一个任务能够操作这个数据,避免“抢占”问题。简单来说,锁就是一种机制,它能帮助你控制多个任务按顺序来操作资源

按照锁的功能来进行分类,iOS常见的锁:自旋、互斥、递归、条件。。。

一、自旋锁

自旋锁的意思就是当资源被占有时,自旋锁不会引起其他调用者休眠,而是让其他调用者自旋,不停的循环访问自旋锁导致调用者处于busy-wait(忙等状态),直到自旋锁的保持者释放锁。自旋锁是为了实现保护共享资源一种锁机制,在任何时刻只能有一个保持者,也就是说在任何时刻只能有一个可执行单元获得锁。也正是因为其他调用者会保持自旋状态,使得在锁的保持者释放锁时能够即刻获得锁,效率非常高。但我们说调用者时刻自旋也是消耗CPU资源的,所以如果自旋锁的使用者保持锁的时间比较短的话,使用自旋锁是非常合适的,因为在锁释放之后省去了唤醒调用者的时间。

1.OSSpinLock(已弃用)

  • OSSpinLock 是一种轻量级的锁。当一个线程获取不到锁时,它不会进入睡眠状态,而是一直循环检查锁是否可用,这叫“自旋”。
  • 缺点OSSpinLock 已经被弃用,因为它容易导致“优先级反转”(低优先级线程获取锁,高优先级线程等待锁释放,造成高优先级线程无法执行)。

2.os_unfair_lock

  • os_unfair_lockOSSpinLock 的替代品。它解决了优先级反转问题,当一个线程无法获取锁时,会立即休眠而不是自旋。

  • 适用场景:用于短时间的锁定操作,轻量、快速。

var unfairLock = os_unfair_lock_s()

func safeMethod() {
    os_unfair_lock_lock(&unfairLock)
    // 执行共享资源的操作
    os_unfair_lock_unlock(&unfairLock)
}

用GCD模拟多线程,看是否输出是否按顺序输出:

import Foundation

// 初始化不公平锁
var unfairLock = os_unfair_lock_s()

// 共享资源(例子:计数器)
var sharedCounter = 0

// 线程安全的方法,增量计数器
func safeIncrement() {
    // 加锁,确保只有一个线程可以访问共享资源
    os_unfair_lock_lock(&unfairLock)
    
    // 临界区:操作共享资源
    sharedCounter += 1
    print("计数器增加: \(sharedCounter)") // 输出当前计数器的值
    
    // 解锁,允许其他线程访问共享资源
    os_unfair_lock_unlock(&unfairLock)
}

// 使用DispatchQueue模拟多线程
let queue = DispatchQueue.global()

// 测试线程安全的方法
for _ in 1...10 {
    queue.async {
        safeIncrement() // 多个线程同时操作计数器
    }
}

输出:

二、互斥锁

互斥锁和自旋锁类似,都是为了解决对某项资源的互斥使用,并且在任意时刻最多只能有一个执行单元获得锁,与自旋锁不同的是,互斥锁在被持有的状态下,其他资源申请者只能进入休眠状态,当锁被释放后,CPU会唤醒资源申请者,然后获得锁并访问资源。

1.pthread_mutex_t

  • pthread_mutex_t 是 POSIX 线程库提供的底层互斥锁,能确保同一时刻只有一个线程访问共享资源。
  • 适用场景:高效多线程编程,适合对性能要求高的场景。
var mutex = pthread_mutex_t()
pthread_mutex_init(&mutex, nil)

func safeMethod() {
    pthread_mutex_lock(&mutex)
    // 执行共享资源的操作
    pthread_mutex_unlock(&mutex)
}

同样的GCD模拟多线程,看是否输出是否按顺序输出:

import Foundation
//初始化互斥锁(Mutex)
var mutex = pthread_mutex_t()
pthread_mutex_init(&mutex, nil)

// 共享资源(例子:计数器)
var sharedCounter = 0

// 线程安全的方法,增量计数器
func safeIncrement() {
    // 加锁,确保只有一个线程可以访问共享资源
    pthread_mutex_lock(&mutex)
    
    // 临界区,操作共享资源
    sharedCounter += 1
    print("计数器增加: \(sharedCounter)") // 输出当前计数器的值

    // 解锁,允许其他线程访问共享资源
    pthread_mutex_unlock(&mutex)
}

// 使用DispatchQueue模拟多线程
let queue = DispatchQueue.global()

// 测试线程安全的方法
for _ in 1...10 {
    queue.async {
        safeIncrement() // 多个线程同时操作计数器
    }
}

输出:

2.NSLock

  • NSLock 是 Cocoa 提供的更高级的互斥锁,它比 pthread_mutex_t 更易于使用。
let lock = NSLock()

func safeMethod() {
    lock.lock()
    // 执行代码
    lock.unlock()
}

GCD模拟多线程:

import Foundation
//初始化互斥锁(NSLock)
var lock = NSLock()

// 共享资源(例子:计数器)
var sharedCounter = 0

// 线程安全的方法,增量计数器
func safeIncrement() {
    // 加锁,确保只有一个线程可以访问共享资源
    lock.lock()
    
    // 临界区,操作共享资源
    sharedCounter += 1
    print("计数器增加: \(sharedCounter)") // 输出当前计数器的值

    // 解锁,允许其他线程访问共享资源
    lock.unlock()
}

// 使用DispatchQueue模拟多线程
let queue = DispatchQueue.global()

// 测试线程安全的方法
for _ in 1...10 {
    queue.async {
        safeIncrement() // 多个线程同时操作计数器
    }
}

3.@synchronized(仅支持 Objective-C)

  • 这是 Objective-C 中提供的自动锁机制,是OC的语法糖,Swift 中无法直接使用。它可以帮助你简化锁定的逻辑。
@synchronized(self) {
    // 执行共享资源操作
}

三、递归锁

递归锁可以被同一线程多次请求,而不会引起死锁,即在多次被同一个线程进行加锁时,不会造成死锁。这主要是用在循环或递归操作中。

递归锁也是通过 pthread_mutex_lock 函数来实现,在函数内部会判断锁的类型,如果显示是递归锁,就允许递归调用,仅仅将一个计数器加一,等到递归完毕之后,所有锁都会释放

1.NSRecursiveLock

  • NSRecursiveLock 是一种递归锁,允许同一个线程多次获取同一把锁而不会导致死锁。这是 NSLock 无法做到的。
  • 适用于需要多次锁定同一资源的场景。
let recursiveLock = NSRecursiveLock()

func recursiveFunction(count: Int) {
    recursiveLock.lock()
    if count > 0 {
        print("Count: \(count)")
        recursiveFunction(count: count - 1)
    }
    recursiveLock.unlock()
}

2.pthread_mutex_t (递归锁)

  • pthread_mutex_t 也可以被设置为递归模式,用法类似 NSRecursiveLock。

四、条件锁

条件是信号量的另一种类型,当某个条件为true时,它允许线程相互发信号。条件通常用于指示资源的可用性或确保任务以特定顺序执行。当线程测试条件时,除非该条件已经为真,否则它将阻塞。它保持阻塞状态,直到其他线程显式更改并发出条件信号为止。条件和互斥锁之间的区别在于,可以允许多个线程同时访问该条件。 

1.pthread_cond_t

  • pthread_cond_t 是一种条件锁,常与 pthread_mutex_t 搭配使用,允许线程在满足特定条件时进行等待或唤醒。
  • 适用场景:用于需要等待某个条件满足的多线程场景。

2.NSCondition

  • NSCondition 是一个高级条件锁,可以让线程根据某些条件来等待或唤醒。
  • NSCondition 的底层是通过条件变量(condition variable) pthread_cond_t 来实现的。条件变量有点像信号量,提供了线程阻塞与信号机制,因此可以用来阻塞某个线程,并等待某个数据就绪,随后唤醒线程,比如常见的生产者-消费者模式。
let condition = NSCondition()
var isReady = false

func producer() {
    condition.lock()
    isReady = true
    condition.signal()  // 唤醒等待中的线程
    condition.unlock()
}

func consumer() {
    condition.lock()
    while !isReady {
        condition.wait()  // 等待条件满足
    }
    // 执行消费操作
    condition.unlock()
}

模拟:

import Foundation

let condition = NSCondition()
var isReady = false

// 生产者方法
func producer() {
    condition.lock()
    print("生产者正在准备资源...")
    isReady = true
    print("资源准备完成,通知消费者")
    condition.signal()  // 唤醒等待的消费者线程
    condition.unlock()
}

// 消费者方法
func consumer() {
    condition.lock()
    print("消费者等待资源...")
    while !isReady {
        condition.wait()  // 等待条件满足
    }
    print("资源已准备好,开始消费资源")
    // 执行消费操作
    condition.unlock()
}

// 模拟并发:使用DispatchQueue进行生产者和消费者的交互
let queue = DispatchQueue.global()

// 消费者等待资源
queue.async {
    consumer()
}

// 模拟生产者延迟生产资源
queue.asyncAfter(deadline: .now() + 2) {
    producer()
}

3.NSConditionLock

  • NSConditionLock 是 NSCondition 的一种变体,基于条件值进行锁定和解锁,适用于更复杂的线程同步场景。

五、信号量

信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。在进入一个关键代码段之前,线程必须获取一个信号量;一旦该关键代码段完成了,那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待直到第一个线程释放信号量。为了完成这个过程,需要创建一个信号量VI,然后将Acquire Semaphore VI以及Release Semaphore VI分别放置在每个关键代码段的首末端。确认这些信号量VI引用的是初始创建的信号。

其实本质上,它通过维护一个计数值来控制同时可以访问某一资源的线程数量

1.dispatch_semaphore_t

  • dispatch_semaphore_t 是 GCD 提供的信号量机制,用于控制同时访问某一资源的线程数量。
  • 适用场景:适合控制并发任务的数量。
let semaphore = DispatchSemaphore(value: 1)

func safeMethod() {
    semaphore.wait()   // 请求资源
    // 执行共享资源操作
    semaphore.signal() // 释放资源
}

2.pthread_mutex_t(作为信号量使用)

  • 可以通过将 pthread_mutex_tpthread_cond_t 配合使用,达到类似信号量的效果。

六、读写锁

读写锁实际是一种特殊的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。这种锁相对于自旋锁而言,能提高并发性,因为在多处理器系统中,它允许同时有多个读者来访问共享资源,最大可能的读者数为实际的逻辑CPU数。写者是排他性的,一个读写锁同时只能有一个写者或多个读者(与CPU数相关),但不能同时既有读者又有写者。

读写锁适合于对数据结构的读次数比写次数多得多的情况. 因为, 读模式锁定时可以共享, 以写模式锁住时意味着独占, 所以读写锁又叫共享-独占锁。

1.pthread_rwlock_t

  • pthread_rwlock_t 是一种读写锁,它允许多个线程同时读取资源,但在写入时会排他性地锁定。
  • 适用场景:适合读多写少的场景。
var rwlock = pthread_rwlock_t()
pthread_rwlock_init(&rwlock, nil)

func readResource() {
    pthread_rwlock_rdlock(&rwlock)  // 加读锁
    // 读取资源
    pthread_rwlock_unlock(&rwlock)  // 解锁
}

func writeResource() {
    pthread_rwlock_wrlock(&rwlock)  // 加写锁
    // 写入资源
    pthread_rwlock_unlock(&rwlock)  // 解锁
}

七、栅栏

栅栏函数在GCD中常用来控制线程同步,在队列中它总是等栅栏之前的任务执行完,然后执行栅栏自己的任务,执行完自己的任务后,再继续执行栅栏后的任务。常用函数有同步栅栏函数(dispatch_barrier_sync)和异步栅栏函数(dispatch_barrier_async)。

import Foundation

// 创建一个并发队列
let queue = DispatchQueue(label: "com.example.concurrentQueue", attributes: .concurrent)

// 异步执行任务1
queue.async {
    print("Task 1")
}

// 异步执行任务2
queue.async {
    print("Task 2")
}

// 使用 barrier 标志的任务,确保在这个任务期间,队列中不会有其他任务执行
queue.async(flags: .barrier) {
    print("Barrier task")
    print("Barrier task2")  // 在屏障任务中执行第二个打印
}

// 异步执行任务3,等待 barrier 任务执行完毕后继续
queue.async {
    print("Task 3")
}

输出顺序

• Task 1 和 Task 2 可能无序输出,因为它们是并发执行的。

• 屏障任务会在之前的任务完成后执行。

• Task 3 将在屏障任务结束后执行。

总结:

参考:

iOS - 线程中常见的几种锁_unlock tryluck-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2157631.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue打包exe之electron-quick-start的npm install 报错

vue打包exe之electron-quick-start的npm install 报错 1、github地址2、问题3、解决4、其他(打包exe)参考 1、github地址 https://github.com/electron/electron-quick-start2、问题 我使用的pnpm install正常安装,执行npm start提示错误 3、解决 在package.js…

Python之一些列表的练习题

1.比较和对比字符串、列表和元组。例如,它们可以容纳哪类内容以及在数据结构上可以做哪些操作。 1. 内容类型:- 字符串: 只能包含字符(文本)。- 列表: 可以包含任意类型的数据,如数字、字符串、其他列表等。- 元组: 可以包含任意类型的数据,与列表类似。3. 操作:(1…

Kaggle-狗种类的识别(Pytorch框架)基本图像识别流程

狗类别实现过程 一. 将数据集按标签分类,将标签转换为数字表示,并制作数据集 二. 搭建网络框架,inception,或者ResNet 三. 选择优化函数,训练模型 数据集制作 首先分析数据集,题中已经很明确告诉有120 种…

头晕,脖子酸痛?颈椎有问题,人就废了一半!颈椎病分3级,不同阶段治疗方法不一样!

每天下午快下班时,在办公室就会看到一种现象: 大家纷纷扭脖子、抬头、耸肩膀......诶,脖子太难受了! 毕竟每天长时间的面对电脑,我们的脖子在承受着巨大的压力。尤其,低头 45 度时,脖子甚至承受…

Fyne ( go跨平台GUI )中文文档- 扩展Fyne (七)

本文档注意参考官网(developer.fyne.io/) 编写, 只保留基本用法 go代码展示为Go 1.16 及更高版本, ide为goland2021.2 这是一个系列文章: Fyne ( go跨平台GUI )中文文档-入门(一)-CSDN博客 Fyne ( go跨平台GUI )中文文档-Fyne总览(二)-CSDN博客 Fyne ( go跨平台GUI…

图像处理软件,常用于照片编辑和修饰

一、简介 1、一款功能强大的图像处理软件,常用于照片编辑和修饰。它提供多种工具和特效,允许用户调整照片的亮度、对比度、色彩、锐化等 二、下载 1、文末有下载链接,不明白可以私聊我哈(麻烦咚咚咚,动动小手给个关注收藏小三连&a…

【掘金量化使用技巧】用日线合成长周期k线

掘金API中的接口最长的周期是‘1d’的,因此周线/月线/年线等数据需要自己进行合成。 基本思路 用日线合成长周期的k线只需要确定好合成的周期以及需要的数据即可。 周期: 一般行情软件上提供年k、月k、周k,我也选择年、月、周再加一个季度频率。 数据:…

图纸加密防泄密软件 | 从设计到交付,2024年值得关注的图纸加密软件大盘点!

图纸者,匠心之凝聚,智慧之结晶。然,信息之海浩瀚无垠,暗流涌动,图纸之安全,实乃企业之头等大事。 故,择一良器,以密护图纸,实为当务之急。 以下,七款图纸加密…

Linux之实战命令01:xargs应用实例(三十五)

简介: CSDN博客专家、《Android系统多媒体进阶实战》一书作者 新书发布:《Android系统多媒体进阶实战》🚀 优质专栏: Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏: 多媒体系统工程师系列【…

高刷显示器哪个好?540Hz才有资格称高刷

高刷显示器哪个好?说实话,540Hz这些才能成为高刷显示器,什么200,240的,都不够高,什么是从容,有我不用才叫从容。下面我们一起来看看540Hz的高刷显示器都有哪些吧! 1.高刷显示器哪个好 - 蚂蚁电…

2024风湿免疫科常用评估量表汇总,附操作步骤与评定标准!

常笑医学整理了5个风湿免疫科常用的评估量表,包括类风湿关节炎患者病情评价(DAS28)、系统性狼疮活动性测定(SLAM)等。这些量表在常笑医学网均支持在线评估、下载和创建项目使用。 01 类风湿关节炎患者病情评价 &#x…

实践中如何选择o1或sonnet3-5?

简述 AI更新太快导致我们不知选择什么使用更好?本文对比了新模型o1系列和Claude-3.5-sonnet的一些特点,针对不同开发场景提供了选择建议,希望能为你提供一些模型选择的参考。 模型对比 o1系列: 优势: 推理能力非常强&#xff0…

【动态规划】两个数组的 dp 问题二

两个数组的 dp 问题 1.正则表达式匹配2.交错字符串3.两个字符串的最小ASCII删除和4.最长重复子数组 点赞👍👍收藏🌟🌟关注💖💖 你的支持是对我最大的鼓励,我们一起努力吧!😃&#x1…

高德地图自定义点标记

const markerContent <div class"custom-content-marker"> <span>摄像机<span> <img src"//a.amap.com/jsapi_demos/static/demo-center/icons/poi-marker-red.png"> </div> marker.value new AMap.Marker({position:…

Ubuntu搭建java开发环境

一&#xff1a;Ubuntu安装 1、下载Ubuntu 24.04.1 LTS 官网下载地址&#xff1a;https://releases.ubuntu.com/24.04.1/ubuntu-24.04.1-desktop-amd64.iso 可以直接点击这里下载 2、使用VMware安装 新建虚拟机 之后一直下一步&#xff0c;到如下界面&#xff0c;选择 刚刚…

【MYSQL】聚合查询、分组查询、联合查询

目录 聚合查询聚合函数count()sum()avg()max()和min()总结 分组查询group by 子句having 子句 联合查询笛卡尔积内连接外连接自连接子查询单行子查询多行子查询from子句使用子查询 合并查询 聚合查询 聚合查询就是针对表中行与行之间的查询。 聚合函数 count() count(列名)&a…

战神5/战神:诸神黄昏/God of War Ragnarok(容量175GB)百度网盘下载

版本介绍 v1.0.612.4312|容量175GB|官方简体中文|支持键盘.鼠标.手柄|赠单板学习补丁 配置要求 战神5/战神&#xff1a;诸神黄昏/God of War Ragnarok 游戏介绍 不灭的北欧传奇 由Santa Monica Studio出品、Jetpack Interactive负责PC移植的佳作《God of War Ragnark》将带您…

python实现语音唤醒

1. 环境 python版本&#xff1a;3.11.9 2.完整代码 import sqlite3 import timefrom funasr import AutoModel import sounddevice as sd import numpy as np from pypinyin import lazy_pinyin# 模型参数设置 chunk_size [0, 10, 5] encoder_chunk_look_back 7 decoder_c…

气膜体育馆:低成本、高效益的体育空间解决方案—轻空间

随着全民健身和健康生活理念的兴起&#xff0c;各类体育场馆需求日益增加。在这样的市场背景下&#xff0c;气膜体育馆凭借其低成本、快速建造以及灵活多变的空间设计&#xff0c;成为现代体育场馆建设的新趋势。气膜技术为体育场馆提供了一种全新的解决方案&#xff0c;让运营…

Tomcat 漏洞复现

1、CVE-2017-12615 1、环境开启 2、首页抓包&#xff0c;修改为 PUT 方式提交 Tomcat允许适用put方法上传任意文件类型&#xff0c;但不允许isp后缀文件上传&#xff0c;因此需要配合 windows的解析漏洞 3、访问上传的jsp文件 4、使用工具进行连接 2、后台弱⼝令部署war包 1…