iOS如何保证线程的安全

1.什么是保证线程的安全,线程为什么会不安全

多个线程同时修改同一资源时可能会以意想不到的方式造成互相干扰,比如一个线程可能覆盖了另一个线程改动的地方,造成数据的错乱。

在设计多线程操作的时候,应该尽量避免线程的交互,如果必须要交互,应该使用同步工具,保证线程交互的时候是安全的。

2.几种常见的同步工具

原子操作 atomic属性,默认系统生成的属性是原子的。原子属性在setter和getter方法上是线程安全的。如果是非原子属性,同时在不同的线程调用setter方法就会产生问题。原子操作其实是用自旋锁实现的。

锁是最常见的同步工具,用来保护临界区(critical section),这些代码段在同一时间只能允许被一个线程访问。这些代码段可能要求同一时间只能有一个用户执行操作,这种代码就需要用锁去保护。

条件 

3.同步会消耗性能

无论是加锁还是使用原子操作都会消耗系统的性能,因此讨论各种同步方法的性能消耗就变得非常重要。

加锁操作伴随的消耗:

从用户态切换到内核态。上下文切换。上下文切换的要点:

1)进程上下文切换可以描述为kernel执行下面的操作

a. 挂起一个进程,并储存该进程当时寄存器和程序计数器的状态

b. 从内存中恢复下一个要执行的进程,恢复该进程原来的状态到寄存器,返回到其上次暂停的执行代码然后继续执行

2)上下文切换只能发生在内核态,所以还会触发用户态与内核态切换

4.自旋锁

自旋锁是为了保护一小段临界区代码,保证这个临界区的操作是原子的,从而避免并发的竞争。如果内核控制路径发现自旋锁开着,就获得自旋锁并执行自己的操作,如果内核控制路径发现自旋锁被另一个cpu上的内核控制路径使用,就等待,等待的过程是忙等,直到自旋锁被释放。所以自旋锁保护的代码段必须非常小,否则等待自旋锁的释放会消耗很多时间。

⚠️:什么是忙等busy-waiting

假设有两个线程,线程a和线程b,分别运行在core0和core1上,如果线程a想通过pthread_spin_lock操纵去得到临界区的锁,而这个锁正在被线程b持有,那么这个时候,core0就会一直运行线程a,线程a一直进行锁请求,直到得到这个锁。

OSSpinLock

__block OSSpinLock theLock = OS_SPINLOCK_INIT;

OSSpinLockLock(&theLock);上锁   OSSpinLockUnlock(&theLock);开锁

⚠️:有人发现自旋锁是不再安全的,因为低优先级线程拿到锁时,高优先级线程进入忙等状态,消耗大量cpu时间,导致低优先级拿不到cpu时间,无法完成任务并释放锁,就一直持有锁,高优先级无法拿到锁。产生了优先级反转。

如果临界区的代码非常少,那么自旋锁的执行效率是很高的。

2.互斥锁pthread_mutex

⚠️:信号量用于线程的同步,互斥锁用于线程的互斥。

信号量用在多线程多任务同步的,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作(大家都在semtake的时候,就阻塞在 哪里)。而互斥锁是用在多线程多任务互斥的,一个线程占用了某一个资源,那么别的线程就无法访问,直到这个线程unlock,其他的线程才开始可以利用这个资源。

哈哈哈我今天终于明白了互斥锁和信号量是不一样的,互斥锁是为了把一个代码块锁住,上锁时,其他任何线程都不能访问被保护的资源。

但是信号量是不一样的,他可以让多个线程安全的同步执行。

互斥的值只能是0或1,信号量的值可以为非负整数。

也就是说,一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为单值信号量是,也可以完成一个资源的互斥访问。

互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到。

互斥锁的使用:

创建:CreateMutex、加锁:pthread_mutex_lock、解锁:pthread_mutex_unlock、销毁pthread_mutex_destroy

3.dispatch_semaphore

dispatch_semaphore_create(long value)生成一个值为value的dispatch_semaphore_t类型的信号量。

dispatch_semaphore_signal(dispatch_semaphore_t deem)

这个方法会使输入的deem信号量的值加1 

long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);

计数大于等于1时不等待并返回该函数,并且计数减1,计数等于0时等待,等待什么呢,如果在timeout时间内这个信号量的值大于0了(信号量值加1了),这个时候就返回该函数向下执行,如果等到了timeout信号量的值依然没有大于0,就继续执行下面的操作。

这个函数的返回结果是long类型的,如果返回的是0代表信号量的值大于0可以执行,如果非0,代表信号量的值等于0,不能执行。

信号量是睡眠等待的,假设有两个线程,线程a和线程b,分别运行在core0和core1上,线程a想要访问一段临界区的代码,但是锁正好被线程b拿着,这个时候线程a不会一直在corea上发请求,corea会把线程a放到等待队列,直到这个锁被b释放了再执行线程a。

4.NSLock

NSLock是oc以对象的形式暴露给开发者的一种锁,内部封装pthread_mutex

常用的四种方法:

NSLock *lock = [[NSLock alloc] init];

[lock lock]; 加锁   [lock unlock]; 解锁  

[lock tryLock];   尝试获取锁

NSDate *date = [[NSDate alloc] initWithTimeIntervalSinceNow:3];

[lock lockBeforeDate:date];在所指定的date时间之前尝试获取锁

5.NSRecursiveLock递归锁

NSRecursiveLock实际上定义的是一个递归锁,这个锁可以被同一线程多次请求,而不会引起死锁。它可以允许同一线程多次加锁,而不会造成死锁。递归锁会跟踪它被lock的次数。每次成功的lock都必须平衡调用unlock操作。只有所有达到这种平衡,锁最后才能被释放,以供其它线程使用。基于NSLock

6.NSConditionLock条件锁

基于NSLock

[lock lockWhenCondition:HAS_DATA];

[lock unlockWithCondition:NO_DATA];

只有满足一定条件的钥匙才能打开这个锁,也只有满足一定条件的锁才能锁上。

7.NSCondition

常用的方法:是一种基于信号量的实现方法

NSCondition *lock = [[NSCondition alloc] init];

[lock lock];上锁

[lock unlock];解锁

[lock wait];这个方法比较特殊,调用之后当前线程直接进入 wait 状态,当其它线程中的该锁执行 signal 或者 broadcast 方法时,线程被唤醒,继续运行之后的方法。

[lock signal]; 可以唤醒一个等待的线程

[lock broadcast];可以唤醒所有等待的线程

NSCondition和信号量的区别:

从上面的实例代码可以看到,一个 dispatch_semaphore_wait(signal, overTime); 方法会去对应一个 dispatch_semaphore_signal(signal); 看起来像NSLock的 lock 和 unlock,其实可以这样理解,区别只在于有信号量这个参数,lock unlock 只能同一时间,一个线程访问被保护的临界区,而如果 dispatch_semaphore 的信号量初始值为 x ,则可以有 x 个线程同时访问被保护的临界区。

8.synchronized

我们最常用的一种加锁机制,其实效率是最低的。他可以让我们不需要显示的去生成锁,而是系统自动生成锁。

[_lock lock];

[_elements addObject:element];

[_lock unlock];

用synchronized去实现:

@synchronized (obj 一个对象) {

        [_elements addObject:element];

}

@synchronized 如何将一个锁和你正在同步的对象关联起来:

当你调用 objc_sync_enter(obj) 时,它用 obj 内存地址的哈希值查找合适的 SyncData,然后将其上锁。当你调用 objc_sync_exit(obj) 时,它查找合适的 SyncData 并将其解锁。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,258评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,335评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,225评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,126评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,140评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,098评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,018评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,857评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,298评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,518评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,400评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,993评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,638评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,661评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容