Objective 锁

加锁

线程安全隐患解决方案

解决方案 使用线程同步技术(按照预定的先后次序进行)

1、OSSpinLock 自旋锁 ( heigh-leve Lock、自旋锁)

#import "<libkern/OSAtomic.h>"


 OSSpinLock _lock = OS_SPINLOCK_INIT;
 OSSpinLockLock(&_lock);
 OSSpinLockUnlock(&_lock);
 OSSpinLockTry(&_lock); //尝试加锁 如果不能加锁 返回No
复制代码
  • 因为是自旋锁( busy-wait) 所以一直cpu占用资源,线程不会休眠,一直等待,所以效率最高。
存在的问题
  • ios10之后过期
  • 优先级反转

由于锁是自旋锁,线程不会休眠,所以当低优先级线程先对操作进行Lock造作后,CPU调度高优先级线程造作,由于低优先级别UnLock就调用高优先级线程。高优先级无法处理该操作,而高优先级线程一直调用CPU资源, 系统等待高优先级线程执行完毕后才给低优先级线程资源。

高优先级线程等待低优先级线程Unlock。低优先级线程等待CPU资源调度。造成类似死锁 导致锁无法释放。相互等待

2、os_unfair_lock (low-level Lock、互斥锁 )

import <os.lock.h>

os_unfair_lock _lock = OS_UNFAIR_LOCK_INIT;
os_unfair_lock_lock(&_lock);
os_unfair_lock_unlock(&_lock);
os_unfair_lock_trylock(&_lock);//尝试加锁 如果不能加锁 返回No
复制代码
  • ios10之后支持

3、ptheard_mutex (low-level Lock、互斥锁)

#import"pthread.h"

    pthread_mutexattr_t attr = {0};
    pthread_mutexattr_init(&attr);
		//设置锁的描述  PTHREAD_MUTEX_DEFAULT为一般互斥锁  PTHREAD_MUTEX_RECURSIVE为递归锁
		//pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE为递归锁); //递归锁
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
    pthread_mutex_init(mutex, &attr);
		//释放  防止内存泄漏
    pthread_mutexattr_destroy(&attr);
复制代码
1、递归锁 PTHREAD_MUTEX_RECURSIVE

根据参数 attr PTHREAD_MUTEX_RECURSIVE 递归锁

PTHREAD_MUTEX_RECURSIVE :允许同一个线程 重复加锁。

2、条件 condition

线程激活信号 不一定会马上执行 先等上一次操作unlock

// 创建锁
- (void) __initLock:(pthread_mutex_t *) mutex {
    pthread_mutexattr_t attr = {0};
    pthread_mutexattr_init(&attr);
	  //互斥锁
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
    pthread_mutex_init(mutex, &attr);
    pthread_mutexattr_destroy(&attr);
    
    pthread_condattr_t condAtt={0};
	  // 创建条件
    pthread_cond_init(&_condition, &condAtt);
    pthread_condattr_destroy(&condAtt);
}
- (instancetype)init
{
    self = [super init];
    if (self) {
        [self __initLock:&_lock];
        
        
       dispatch_queue_t queue = dispatch_queue_create(0, DISPATCH_QUEUE_CONCURRENT);
        dispatch_async(queue, ^{
            [self test2];
        });
        dispatch_async(queue, ^{
            sleep(2);
            [self test1];
            
        });
    }
    return self;
}
- (void)test1 {
    NSLog(@"%@",[NSThread currentThread]);
  	// lock
    pthread_mutex_lock(&_lock);
    NSLog(@"1");
	  //对condition发送信号
    pthread_cond_signal( &_condition);
	  NSLog(@"3");
	  //unlock
    pthread_mutex_unlock(&_lock);
	  NSLog(@"4");
}

- (void)test2 {
    NSLog(@"%@",[NSThread currentThread]);
  	// lock
    pthread_mutex_lock(&_lock);
  	// 等待condition发送信号
    pthread_cond_wait(&_condition, &_lock);
    NSLog(@"2");
  	//unlock
    pthread_mutex_unlock(&_lock);
}

复制代码

输出结果为

2019-09-03 14:55:49.650729+0800 GCD[1918:36391522] 1
2019-09-03 14:55:49.650896+0800 GCD[1918:36391522] 3
2019-09-03 14:55:49.651025+0800 GCD[1918:36391522] 4
2019-09-03 14:55:49.651031+0800 GCD[1918:36391523] 2
复制代码

上述代码 无论test1 和test2谁先调用 test2 依赖于test1的调用 只有当test1中执行 pthread_cond_signal( &_condition); test2中才会继续执行, 执行步骤为:

  1. Test2
  2. Test2 中lock pthread_mutex_lock(&_lock);
  3. 等待condition发送信号pthread_cond_wait(&_condition, &_lock); 此时unlock锁 并且线程休眠
  4. 2s后执行Test1
  5. Test1 中lock pthread_mutex_lock(&_lock);
  6. 输出1
  7. //对condition发送信号 pthread_cond_signal( &_condition); test2线程被激活
  8. 输出3
  9. Test1中解锁unlock
  10. Test2 执行lock操作中继续执行 输出2
  11. test2中解锁
  • 8-9 和10-11是同时进行
  • condition 不仅可以发送信号signal 还可以发送广播 pthread_cond_boradcast 激活所有pthread_cond_wait
注意点
  1. 使用condition的时候 不要使用同一个线程调用wait和signal 因为线程调度万wait的时候为休眠状态,无法执行signal 可以试试上面的代码 把并发队列换成串行队列试一下。
  2. 结构体初始化只能直接初始化 如下图所示

4、NSLock

NSLock是对mutex PTHREAD_MUTEX_DEFAULT 普通锁的封装

- (void)lock;// 加锁
- (void)unlock;//解锁
- (BOOL)tryLock;// 尝试是否可以加锁 如果可以 直接加锁返回YES
- (BOOL)lockBeforeDate:(NSDate *)limit; //在一定时间内是否可以加锁 如果可以 直接加锁返回YES
复制代码

5、NSRecursiveLock

NSLock是对mutex PTHREAD_MUTEX_RECURSIVE递归锁的封装

API和NSLock一样

- (void)lock;// 加锁
- (void)unlock;//解锁
- (BOOL)tryLock;// 尝试是否可以加锁 如果可以 直接加锁返回YES
- (BOOL)lockBeforeDate:(NSDate *)limit; //在一定时间内是否可以加锁 如果可以 直接加锁返回YES
复制代码

6、NSCondition

NSCondition是对mutex和cond的封装

//用法和mutex condtion 一样  可以参考上面代码
- (void)lock;// 加锁
- (void)unlock;//解锁
- (void)wait;//等待
- (BOOL)waitUntilDate:(NSDate *)limit; //等待多久  如果超时就不等了 就直接执行
- (void)signal; // 信号量
- (void)broadcast; //广播
复制代码

7、NSConditionLock

NSConditionLock是对NSCondition的封装

- (void)lock;// 加锁
- (void)unlock;//解锁
- (void)lockWhenCondition:(NSInteger)condition; //休眠 直到符合conditon后  激活并lock
- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition; // 解锁 修改condtion的值
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;
复制代码

8、Dispatch_semaphore

Dispatch_semaphore 可以控制线程的个数 当控制线程个数为1的时候 能确保同时只有1条线程去访问,已达到确保线程安全的目的

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

// 如果信号量<=0 当前线程就会键入休眠状态 知道信号量的值>0
// 如果信号值>0 就-1 然后执行下面代码
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
[super __saleTicket];
// 信号量+1
dispatch_semaphore_signal(_semaphore);
复制代码

9、dispatch_quueue(DISPATCH_QUQUQ_SERIAL)

串行队列,当在一个串行队列执行的时候,只有一个线程,能确保线程安全

10、@synchronised

以前:对mutex递归锁的封装

现在:os_unfair_recursive_lock的封装

首先打开断点汇编模式

obj4源码中objc_sync.mm

底层存储 hash表 key :传进去的对象

class recursive_mutex_tt : nocopy_t {
    os_unfair_recursive_lock mLock;
	  ...
}
using recursive_mutex_t = recursive_mutex_tt<LOCKDEBUG>;

typedef struct alignas(CacheLineSize) SyncData {
    struct SyncData* nextData;
    DisguisedPtr<objc_object> object;
    int32_t threadCount;  // number of THREADS using this block
    recursive_mutex_t mutex;
} SyncData;

int objc_sync_enter(id obj)
{
    int result = OBJC_SYNC_SUCCESS;

    if (obj) {
        SyncData* data = id2data(obj, ACQUIRE);
        assert(data);
        data->mutex.lock();
    } else {
        // @synchronized(nil) does nothing
        if (DebugNilSync) {
            _objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
        }
        objc_sync_nil();
    }

    return result;
}

复制代码
int objc_sync_exit(id obj)
{
    int result = OBJC_SYNC_SUCCESS;
    
    if (obj) {
        SyncData* data = id2data(obj, RELEASE);
        if (!data) {
            result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
        } else {
            bool okay = data->mutex.tryUnlock();
            if (!okay) {
                result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
            }
        }
    } else {
        // @synchronized(nil) does nothing
    }
	

    return result;
}
复制代码

至于耗时,测的时候每次都不太一样,大家自己测一下吧~

本文分享自微信公众号 - 老沙课堂(gh_f73a6b772d4f),作者:rui4u

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-09-03

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 据结构与算法(十) AVL树

    因为无法改变添加删除顺序(用户操作决定),所以在每次操作之后,让二叉树达到平衡状态。

    老沙
  • isa详解(二)cache和散列表

    源码中查找 objc_destructinstance 销毁一个实例对象。发现需要进行相关判断。所以如果没有的话。释放更快

    老沙
  • 据结构与算法(八) 二叉树的练习

    •设定levelSize初始值为1(只有一个根节点)•当进行while循环的时候 levelsize-- 操作。因为levelSize和每层节点个数相等。所以当...

    老沙
  • UNPv1第二十三章:线程

    在传统的UNIX模型中,当一个进程需要由另一个实体执行某件事时,该进程派生(fork)一个子进程,让子进程去进行处理。UNIX下的大多数网络服务器程序都是这么编...

    提莫队长
  • 线程概念简介 什么是线程 多线程上篇(七)

    在20世纪 60年代人们提出了进程的概念后,在OS中一直都是以进程作为能拥有资源和独立运行的基本单位的。

    noteless
  • ActionsScript 3.0简易涂鸦板

    需要注意的是,该例子使用到了Button (属于flash cs3/cs4 中fl组件,位于fl.controls包下,而此编辑器默认不包含fl包)

    meteoric
  • 七种方案!探讨Redis分布式锁的正确使用姿势

    日常开发中,秒杀下单、抢红包等等业务场景,都需要用到分布式锁。而Redis非常适合作为分布式锁使用。本文将分七个方案展开,跟大家探讨Redis分布式锁的正确使用...

    捡田螺的小男孩
  • 七种方案!探讨Redis分布式锁的正确使用姿势!

    日常开发中,秒杀下单、抢红包等等业务场景,都需要用到分布式锁。而Redis非常适合作为分布式锁使用。本文将分七个方案展开,跟大家探讨Redis分布式锁的正确使用...

    macrozheng
  • 这3个并发编程的核心,你一定要知道!

    如果你已经工作了,那么你一定听说过或者正在应用敏捷开发模式来交付日常的工作任务,我们就用你熟悉的流程来解释这三个核心问题

    程序员追风
  • 学并发编程,透彻理解这三个核心是关键

    上一篇文章这次走进并发的世界,请不要错过 给大家带了并发编程的开胃菜,接下来我们逐步上正餐,在吃正餐之前,我还要引用那首诗词: 「横看成岭侧成峰,远近高低各不同...

    码农小胖哥

扫码关注云+社区

领取腾讯云代金券