001@多用派发队列,少用同步锁

多用派发队列,少用同步锁

《Effective Objective-C》中第41条: 多用派发队列,少用同步锁。多个线程执行同一份代码时,很可能会造成数据不同步,这是推荐采用GCD来为代码加锁解决问题。

锁与线程安全

快速回顾一下锁

锁(lock) 或者 互斥锁(mutex) 是一种结构,用来保证一段代码在同一时刻只有一个线程执行。它们通常被用来保证多线程访问同一可变数据结构时的数据一致性。主要有下面几种锁:

  • 阻塞锁(Blocking locks):常见的表现形式是当前线程会进入休眠,直到被其他线程释放。
  • 自旋锁(Spinlocks):使用一个循环不断地检查锁是否被释放。如果等待情况很少话这种锁是非常高效的,相反,等待情况非常多的情况下会浪费 CPU 时间。
  • (Reader/writer locks):允许多个读线程同时进入一段代码,但当写线程获取锁时,其他线程(包括读取器)只能等待。这是非常有用的,因为大多数数据结构读取时是线程安全的,但当其他线程边读边写时就不安全了。
  • 递归锁(Recursive locks):允许单个线程多次获取相同的锁。非递归锁被同一线程重复获取时可能会导致死锁、崩溃或其他错误行为。

APIs

苹果提供了一系列不同的锁 API,下面列出了其中一些:

  • pthread_mutex_t
  • pthread_rwlock_t
  • dispatch_queue_t
  • NSOperationQueue 当配置为 serial 时
  • NSLock
  • OSSpinLock

除此之外,Objective-C 提供了 @synchronized 语法结构,它其实就是封装了 pthread_mutex_t 。与其他 API 不同的是,@synchronized 并未使用专门的锁对象,它可以将任意 Objective-C 对象视为锁。@synchronized(someObject) 区域会阻止其他 @synchronized(someObject) 区域访问同一对象指针。不同的 API 有不同的行为和能力:

  • pthread_mutex_t 是一个可选择性地配置为递归锁的阻塞锁;
  • pthread_rwlock_t 是一个阻塞读写锁;
  • dispatch_queue_t 可以用作阻塞锁,也可以通过使用 barrier block 配置一个同步队列作为读写锁,还支持异步执行加锁代码;
  • NSOperationQueue 可以用作阻塞锁。与 dispatch_queue_t 一样,支持异步执行加锁代码。
  • NSLock 是 Objective-C 类的阻塞锁,它的同伴类 NSRecursiveLock 是递归锁。
  • OSSpinLock 顾名思义,是一个自旋锁。 最后,@synchronized 是一个阻塞递归锁。

OC中的方法锁方法

互斥锁块@synchronized

// 1、创建一个锁,等待块执行完毕
-(vodi)synchronizedMethod {
    @synchronized(self) {
        //Safe
    }
}
复制代码

NSLock对象

// 2、另外的一个方法时使用NSLock对象
_lock = [[NSLock alloc] init];
- (void)synchronizedMethod {
    [_lock lock]
    // Safe
    [_lock unlock]
}
复制代码

互斥锁pthread_mutex_lock

/// 3、互斥锁 YYCache
pthread_mutex_init(&_lock, NULL);

- (NSUInteger)totalCost {
    pthread_mutex_lock(&_lock);
    NSUInteger totalCost = _lru->_totalCost;
    pthread_mutex_unlock(&_lock);
    return totalCost;
}

- (void)dealloc {
    ...
    
    //销毁互斥锁
    pthread_mutex_destroy(&_lock);
}
复制代码

信号量

_lock = dispatch_semaphore_create(1);

复制代码

然后使用了宏来代替加锁解锁的代码:

#define Lock() dispatch_semaphore_wait(self->_lock, DISPATCH_TIME_FOREVER)
#define Unlock() dispatch_semaphore_signal(self->_lock)
复制代码

使用:

- (BOOL)containsObjectForKey:(NSString *)key {
    if (!key) return NO;
    Lock();
    BOOL contains = [_kv itemExistsForKey:key];
    Unlock();
    return contains;
}
复制代码

滥用@synchronized会减低代码效率,极端情况下回出现死锁(deadlock)现象。也可以使用NSRecursiveLock这种递归锁,这样线程多次持有该锁,而不会出现死锁。

虽然加了锁,但是没法保证绝对的线程安全。因为一个线程多次调用获取方法(getter),每次取到的结果未必相同。在两次操作之间,可能会有其他线程写入新的属性值。

队列

把设置操作与获取操作都安排在序列化的队列里面执行,这样针对属性的访问操作就都同步了。

串行同步队列

_syncQueue = dispatch_queue_create("com.effectiveobjectivec.syncQueue", NULL);

//读取字符串
- (NSString*)someString {
     __block NSString *localSomeString;
    dispatch_sync(_syncQueue, ^{
        localSomeString = _someString;
    });
    return localSomeString;
}

//设置字符串
- (void)setSomeString:(NSString*)someString {
    // 这里也可以优化成异步派发 dispatch_async
     dispatch_sync(_syncQueue, ^{
        _someString = someString;
    });
}

复制代码

将同步与异步派发派发结合起来,可以实现与普通加锁机制一样的同步行为,这么做却不会阻塞执行异步派发的的线程

并发队列

_syncQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

//读取字符串
- (NSString*)someString {
     __block NSString *localSomeString;
     dispatch_sync(_syncQueue, ^{
        localSomeString = _someString;
    });
     return localSomeString;
}

//设置字符串
- (void)setSomeString:(NSString*)someString {
    // 栅栏函数
     dispatch_barrier_async(_syncQueue, ^{
        _someString = someString;
    });
}

复制代码

同步队列及栅栏块,可以零同步行文更加高效

Swift中的应用

同步锁

Swift中没有*@synchronized* ,但是可以使用objc_sync_enter,这是@synchronized的底层实现

func synced(_ lock: Any, closure: () -> ()) {
    objc_sync_enter(lock)
    closure()
    objc_sync_exit(lock)
}

// 使用
synced(self) {
    println("This is a synchronized closure")
}

复制代码

也可以这样进行分钟:

// 也可以这样使用
{ 
    objc_sync_enter(lock)
    defer { objc_sync_exit(lock) }
    //
    // code of critical section goes here
    //
} // <-- 退出此块时释放锁定

// 如果我们加上异常的话,stackoverflow中有一个我觉得写的比较好的方法:
extension NSObject {

    func synchronized<T>(lockObj: AnyObject!, closure: () throws -> T) rethrows ->  T
    {
        objc_sync_enter(lockObj)
        defer {
            objc_sync_exit(lockObj)
        }

        return try closure()
    }
}
复制代码

使用效果如下:

class Foo: NSObject {
    func test() {
        print("1")
        objc_sync_enter(self)
        defer {
            objc_sync_exit(self)
            print("3")
        }

        print("2")
    }
}


class Foo2: Foo {
    override func test() {
        super.test()

        print("11")
        objc_sync_enter(self)
        defer {
            print("33")
            objc_sync_exit(self)
        }

        print("22")
    }
}

let test = Foo2()
test.test()
复制代码

打印结果如下

1
2
3
11
22
33
复制代码

信号量

static private var syncSemaphores: [String: DispatchSemaphore] = [:]

    static func synced(_ lock: String, closure: () -> ()) {

        //get the semaphore or create it
        var semaphore = syncSemaphores[lock]
        if semaphore == nil {
            semaphore = DispatchSemaphore(value: 1)
            syncSemaphores[lock] = semaphore
        }

        //lock semaphore
        semaphore!.wait()

        //execute closure
        closure()

        //unlock semaphore
        semaphore!.signal()
    }
复制代码

并发队列

方法与OC中是一样的,在取值操作与赋值操作

public class UserData {
    private let myPropertyQueue = dispatch_queue_create("com.example.mygreatapp.property", DISPATCH_QUEUE_CONCURRENT)

    private var _myProperty = "" // Backing storage
    public var myProperty: String {
        get {
            var result = ""
            dispatch_sync(myPropertyQueue) {
                result = self._myProperty
            }
            return result
        }

        set {
            dispatch_barrier_async(myPropertyQueue) {
                self._myProperty = newValue
            }
        }
    }
}
复制代码
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值