美文网首页DevSupportiOS开发 技术集锦
线程安全的NSMutableDictionary

线程安全的NSMutableDictionary

作者: iOS104 | 来源:发表于2017-06-09 12:00 被阅读332次

    NSDictionary是线程安全的,NSMutableDictionary是线程不安全的。
    利用锁来保证线程的安全。

    代码地址,欢迎star

    测试方式:

    • 对NSMutableDictionary进行100000次set和get操作。
    • 设备为iPhone 6

    现在有几种方式可以保证线程安全

    • @synchronized
    • NSLock
    • OSSpinLock
    • GCD semaphore
    • GCD serial queue
    • pthread mutex

    在子线程串行执行结果:

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            double then, now;
            
            then = CFAbsoluteTimeGetCurrent();
            for (NSUInteger i = 0; i < _iterationsCount; i++)
            {
                [_syncDictionary setObject:@(i) forKey:@(i)];
                [_syncDictionary objectForKey:@(i)];
            }
            now = CFAbsoluteTimeGetCurrent();
            
            dispatch_async(dispatch_get_main_queue(), ^{
                self.synchronizedLabel.text = [NSString stringWithFormat:@"@synchronized: %f sec\n", now-then];
            });
            
            
            then = CFAbsoluteTimeGetCurrent();
            for (NSUInteger i = 0; i < _iterationsCount; i++)
            {
                [_nsLockDictionary setObject:@(i) forKey:@(i)];
                [_nsLockDictionary objectForKey:@(i)];
            }
            now = CFAbsoluteTimeGetCurrent();
            
            dispatch_async(dispatch_get_main_queue(), ^{
                self.nsLockLabel.text = [NSString stringWithFormat:@"NSLock: %f sec\n", now-then];
            });
            
            then = CFAbsoluteTimeGetCurrent();
            for (NSUInteger i = 0; i < _iterationsCount; i++)
            {
                [_spinLockDictionary setObject:@(i) forKey:@(i)];
                [_spinLockDictionary objectForKey:@(i)];
            }
            now = CFAbsoluteTimeGetCurrent();
            
            dispatch_async(dispatch_get_main_queue(), ^{
                self.osSpinLockLabel.text = [NSString stringWithFormat:@"OSSpinLock: %f sec\n", now-then];
            });
            
            then = CFAbsoluteTimeGetCurrent();
            for (NSUInteger i = 0; i < _iterationsCount; i++)
            {
                [_semaphoreDictionary setObject:@(i) forKey:@(i)];
                [_semaphoreDictionary objectForKey:@(i)];
            }
            now = CFAbsoluteTimeGetCurrent();
            
            dispatch_async(dispatch_get_main_queue(), ^{
                self.gcdSemaphoreLabel.text = [NSString stringWithFormat:@"GCD Semaphore: %f sec\n", now-then];
            });
            
            then = CFAbsoluteTimeGetCurrent();
            for (NSUInteger i = 0; i < _iterationsCount; i++)
            {
                [_queueDictionary setObject:@(i) forKey:@(i)];
                [_queueDictionary objectForKey:@(i)];
            }
            now = CFAbsoluteTimeGetCurrent();
            
            dispatch_async(dispatch_get_main_queue(), ^{
                self.gcdQueueLabel.text = [NSString stringWithFormat:@"GCD Queue: %f sec\n", now-then];
            });
            
            then = CFAbsoluteTimeGetCurrent();
            for (NSUInteger i = 0; i < _iterationsCount; i++)
            {
                [_ptMutexDictionary setObject:@(i) forKey:@(i)];
                [_ptMutexDictionary objectForKey:@(i)];
            }
            now = CFAbsoluteTimeGetCurrent();
            
            dispatch_async(dispatch_get_main_queue(), ^{
                self.pthreadMutexLabel.text = [NSString stringWithFormat:@"PThread mutex: %f sec\n", now-then];
                
                self.twoBlockTestButton.enabled = YES;
                self.lockUnlockTestButton.enabled = YES;
            });
            
        });
    
    image.png

    线程安全的NSMutableDictionary,在两个并发的block中执行100000次set,get

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            
            double then, now;
            dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
            
            then = CFAbsoluteTimeGetCurrent();
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                for (NSUInteger i = 0; i < _iterationsCount; i++)
                {
                    [_syncDictionary setObject:@(i) forKey:@(i)];
                    [_syncDictionary objectForKey:@(i)];
                }
                
                dispatch_semaphore_signal(semaphore);
            });
            
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                for (NSUInteger i = 0; i < _iterationsCount; i++)
                {
                    [_syncDictionary setObject:@(i) forKey:@(i)];
                    [_syncDictionary objectForKey:@(i)];
                }
                
                dispatch_semaphore_signal(semaphore);
            });
            
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            now = CFAbsoluteTimeGetCurrent();
            
            dispatch_async(dispatch_get_main_queue(), ^{
                self.synchronizedLabel.text = [NSString stringWithFormat:@"@synchronized: %f sec\n", now-then];
            });
            
            
            then = CFAbsoluteTimeGetCurrent();
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                for (NSUInteger i = 0; i < _iterationsCount; i++)
                {
                    [_nsLockDictionary setObject:@(i) forKey:@(i)];
                    [_nsLockDictionary objectForKey:@(i)];
                }
                
                dispatch_semaphore_signal(semaphore);
            });
            
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                for (NSUInteger i = 0; i < _iterationsCount; i++)
                {
                    [_nsLockDictionary setObject:@(i) forKey:@(i)];
                    [_nsLockDictionary objectForKey:@(i)];
                }
                
                dispatch_semaphore_signal(semaphore);
            });
            
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            
            now = CFAbsoluteTimeGetCurrent();
            
            dispatch_async(dispatch_get_main_queue(), ^{
                self.nsLockLabel.text = [NSString stringWithFormat:@"NSLock: %f sec\n", now-then];
            });
            
            then = CFAbsoluteTimeGetCurrent();
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                for (NSUInteger i = 0; i < _iterationsCount; i++)
                {
                    [_spinLockDictionary setObject:@(i) forKey:@(i)];
                    [_spinLockDictionary objectForKey:@(i)];
                }
                
                dispatch_semaphore_signal(semaphore);
            });
            
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                for (NSUInteger i = 0; i < _iterationsCount; i++)
                {
                    [_spinLockDictionary setObject:@(i) forKey:@(i)];
                    [_spinLockDictionary objectForKey:@(i)];
                }
                
                dispatch_semaphore_signal(semaphore);
            });
            
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            now = CFAbsoluteTimeGetCurrent();
            
            dispatch_async(dispatch_get_main_queue(), ^{
                self.osSpinLockLabel.text = [NSString stringWithFormat:@"OSSpinLock: %f sec\n", now-then];
            });
            
            then = CFAbsoluteTimeGetCurrent();
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                for (NSUInteger i = 0; i < _iterationsCount; i++)
                {
                    [_semaphoreDictionary setObject:@(i) forKey:@(i)];
                    [_semaphoreDictionary objectForKey:@(i)];
                }
                
                dispatch_semaphore_signal(semaphore);
            });
            
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                for (NSUInteger i = 0; i < _iterationsCount; i++)
                {
                    [_semaphoreDictionary setObject:@(i) forKey:@(i)];
                    [_semaphoreDictionary objectForKey:@(i)];
                }
                
                dispatch_semaphore_signal(semaphore);
            });
            
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            now = CFAbsoluteTimeGetCurrent();
            
            dispatch_async(dispatch_get_main_queue(), ^{
                self.gcdSemaphoreLabel.text = [NSString stringWithFormat:@"GCD Semaphore: %f sec\n", now-then];
            });
            
            then = CFAbsoluteTimeGetCurrent();
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                for (NSUInteger i = 0; i < _iterationsCount; i++)
                {
                    [_queueDictionary setObject:@(i) forKey:@(i)];
                    [_queueDictionary objectForKey:@(i)];
                }
                
                dispatch_semaphore_signal(semaphore);
            });
            
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                for (NSUInteger i = 0; i < _iterationsCount; i++)
                {
                    [_queueDictionary setObject:@(i) forKey:@(i)];
                    [_queueDictionary objectForKey:@(i)];
                }
                
                dispatch_semaphore_signal(semaphore);
            });
            
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            now = CFAbsoluteTimeGetCurrent();
            
            dispatch_async(dispatch_get_main_queue(), ^{
                self.gcdQueueLabel.text = [NSString stringWithFormat:@"GCD Queue: %f sec\n", now-then];
            });
            
            then = CFAbsoluteTimeGetCurrent();
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                for (NSUInteger i = 0; i < _iterationsCount; i++)
                {
                    [_ptMutexDictionary setObject:@(i) forKey:@(i)];
                    [_ptMutexDictionary objectForKey:@(i)];
                }
                
                dispatch_semaphore_signal(semaphore);
            });
            
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                for (NSUInteger i = 0; i < _iterationsCount; i++)
                {
                    [_ptMutexDictionary setObject:@(i) forKey:@(i)];
                    [_ptMutexDictionary objectForKey:@(i)];
                }
                
                dispatch_semaphore_signal(semaphore);
            });
            
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            now = CFAbsoluteTimeGetCurrent();
            
            dispatch_async(dispatch_get_main_queue(), ^{
                self.pthreadMutexLabel.text = [NSString stringWithFormat:@"PThread mutex: %f sec\n", now-then];
                
                self.twoBlockTestButton.enabled = YES;
                self.lockUnlockTestButton.enabled = YES;
            });
        });
    
    image.png

    在并发的线程安全的NSMutableDictionary,性能最好的是OSSpinLock。

    自旋锁的优点:

    • 自旋锁,和互斥锁类似,都是为了保证线程安全的锁。但二者的区别是不一样的,对于互斥锁,当一个线程获得这个锁之后,其他想要获得此锁的线程将会被阻塞,直到该锁被释放。
    • 但自选锁不一样,当一个线程获得锁之后,其他线程将会一直循环在哪里查看是否该锁被释放。所以,此锁比较适用于锁的持有者保存时间较短的情况下。

    但是OSSpinLock不安全。

    • 新版 iOS 中,系统维护了 5 个不同的线程优先级/QoS: background,utility,default,user-initiated,user-interactive。高优先级线程始终会在低优先级线程前执行,一个线程不会受到比它更低优先级线程的干扰。这种线程调度算法会产生潜在的优先级反转问题,从而破坏了 spin lock。

    • 具体来说,如果一个低优先级的线程获得锁并访问共享资源,这时一个高优先级的线程也尝试获得这个锁,它会处于 spin lock 的忙等状态从而占用大量 CPU。此时低优先级线程无法与高优先级线程争夺 CPU 时间,从而导致任务迟迟完不成、无法释放 lock。这并不只是理论上的问题,libobjc 已经遇到了很多次这个问题了,于是苹果的工程师停用了 OSSpinLock。

    os_unfair_lock

    自旋锁已经不在安全,然后苹果又整出来个 os_unfair_lock_t
    这个锁解决了优先级反转问题。

        os_unfair_lock_t unfairLock;
        unfairLock = &(OS_UNFAIR_LOCK_INIT);
        os_unfair_lock_lock(unfairLock);
        os_unfair_lock_unlock(unfairLock);
    

    结论

    所以如果同一线程优先级可以用,OSSpinLock。如果不在同一线程优先级的话在iOS 10用os_unfair_lock

    相关文章

      网友评论

        本文标题:线程安全的NSMutableDictionary

        本文链接:https://www.haomeiwen.com/subject/togxqxtx.html