iOS_Thread Safety

  • In the development process of the project, in order to improve the user experience and improve the efficiency of the program, we often use multi-threading.
  • The resources used by multithreading are shared by multiple threads, and it is inevitable to encounter the problem of resource competition. Therefore, we need to add locks to threads to ensure thread safety.
  • Add various locks to ensure that only one thread accesses the same data within the same period of time.
  • Currently there are the following types of locks:@synchronized、NSLock、NSConditionLock、NSRecursiveLock、NSCondition、pthread_mutex、pthread_rwlock、POSIX Conditions、OSSpinLock、os_unfair_lock
  • In addition to locks, there is a special processing method, using the semaphore mechanism of GCDdispatch_semaphore


@synchronized<mutex>

  • A convenient way to create a mutex that does everything any other mutex does
  • Inside the brackets (anyObject) is the lock object. When setting the lock object, it must be ensured that multiple threads access the same object. The lock object must be unique and must be of the id type; the operation should be as few as possible, because the more codes, The less efficient.
  • If you pass the same identifier (lock object) in different threads, the one who acquires the lock first will lock the code block, and the other thread will be blocked. If you pass a different identifier, it will not cause thread block.
  • Code example:
- (void)threadSafeSynchronziedWithOnject:(id)anyObject{
    // anyObject 即锁对象
    @synchronized (anyObject) {
        // 编写互斥操作代码
    }
}

NSLock<mutex>

  • NSLockImplemented the most basic mutex and followed NSLockingthe protocol
  • Lock and unlock with lockandunlock
  • Code example:
- (void)threadSafeNSLock{
    NSLock *oneLock = [[NSLock alloc]init];
    [oneLock lock]; // 加锁
    [oneLock unlock];  // 解锁
}

NSConditionLock<condition lock>

  • Conditional lock: It can be locked and unlocked under certain conditions.

  • It is very similar to NSCondition, but implemented differently.

  • Followed NSLockingthe protocol .

  • It can be used when two threads need to be executed in a specific order, such as the producer-consumer model NSConditionLock.

    • When the producer executes, the consumer can acquire the lock through a specific condition, and when the producer finishes executing, it will unlock the lock, and then set the condition of the lock as the condition for waking up the consumer thread. The calls to lock and unlock can be combined at will, lockand used unlockWithCondition:in conjunction with ; lockWhenCondition:and used in unlockconjunction with .
  • When the producer releases the lock, set the condition ( condition值) to 10. In this way, the consumer can obtain the lock and execute the program. If the condition given by the consumer to acquire the lock ( condition值) is inconsistent with the condition given when the producer releases the lock ( condition值), the consumer will never be able to acquire the lock, nor will it be able to execute the program. Similarly, if the conditions given by the consumer to release the lock are inconsistent with the conditions given by the producer to acquire the lock, the producer will not be able to acquire the lock, and the program will not be able to execute.

  • Code address:

- (void)threadSafeNSConditionLock{
    self.conditionLock = [[NSConditionLock alloc]init];
    NSThread *threadOne = [[NSThread alloc]initWithTarget:self selector:@selector(producer) object:nil];
    threadOne.name = @"threadOne";
    NSThread *threadTwo = [[NSThread alloc]initWithTarget:self selector:@selector(consumer) object:nil];
    threadTwo.name = @"threadTwo";
    [threadOne start];
    [threadTwo start];
    
}
- (void)producer {
    while (YES) {
        [self.conditionLock lock];
        NSLog(@"produce something");
        [self.conditionLock unlockWithCondition:10];
    }
}

- (void)consumer {
    while (YES) {
        [self.conditionLock lockWhenCondition:10];
        NSLog(@"consumer something");
        [self.conditionLock unlockWithCondition:0];
    }
}

NSRecursiveLock<recursive lock>

  • Recursive lock: Can be acquired multiple times by a thread without causing deadlock.
  • It records the number of successful lock acquisitions. Every time a lock is successfully acquired, there must be a matching release lock corresponding to it, so as not to cause deadlock.
  • Only when all locks are released can other threads acquire locks.
  • Follow NSLockingthe protocol.
  • Code example:
@property (nonatomic, strong) NSRecursiveLock *recursiveLock;

- (void)threadSafeNSRecursiveLock{
    self.recursiveLock = [[NSRecursiveLock alloc] init];
    
    [self RecuriveLockFuncWith:5];
}
- (void)RecuriveLockFuncWith:(int)value{
    [self.recursiveLock lock];
    // 每次加锁 recursion count 加1
    if (value != 0)
    {
        --value;
        [self RecuriveLockFuncWith:value];
    }
    // 每次解锁 recursion count 减1
    // 当recursion count为零时,完全解锁
    [self.recursiveLock unlock];
}

NSCondition

  • It can realize the scheduling of different threads.
  • A thread is blocked by a certain condition until another thread meets the condition and sends a signal to the thread so that the thread can execute correctly.
  • For example, you can start a thread to download pictures, and a thread to process pictures. In this case, the thread that needs to process the picture will be blocked because there is no picture. When the download thread finishes downloading, it meets the needs of the thread that needs to process the picture. In this way, a signal can be given to let the thread that processes the picture resume running.
  • Follow NSLockingthe protocol.
  • Code example:
- (void)threadSafeNSCondition{
    self.condition = [[NSCondition alloc]init];
    NSThread *threadOne = [[NSThread alloc]initWithTarget:self selector:@selector(download) object:nil];
    threadOne.name = @"threadOne";
    NSThread *threadTwo = [[NSThread alloc]initWithTarget:self selector:@selector(doStuffWithDownloadPicture) object:nil];
    threadTwo.name = @"threadTwo";
    [threadOne start];
    [threadTwo start];
    
}
- (void)download {
    @autoreleasepool {
        [self.condition lock];
        NSLog(@"子线程%@下载文件",[NSThread currentThread].name);
        
        if (self.downloadFinish) { // 下载结束后,给另一个线程发送信号,唤起另一个处理程序
            [self.condition signal];
            [self.condition unlock];
        }
    }
    
}
- (void)doStuffWithDownloadPicture {
    @autoreleasepool {
        [self.condition lock];
        while (self.downloadFinish == NO) {
            [self.condition wait];
        }
        NSLog(@"子线程%@处理下载后的文件",[NSThread currentThread].name);
        [self.condition unlock];
    }
}

pthread_mutex<mutex>

  • Need to import framework<pthread.h>
  • A super easy-to-use mutex
  • When using, only need to initialize apthread_mutex_t
  • use pthread_mutex_lockto lock
  • use pthread_mutex_unlockto unlock,
  • When you are done using it, remember to call pthread_mutex_destroyto destroy the lock
  • Code example:
- (void)threadSafePthread_mutex{
    // 设置锁类型
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    // 初始化锁
    pthread_mutex_t lock;
    pthread_mutex_init(&lock,&attr);
    // 加锁
    pthread_mutex_lock(&lock);
    // 数据操作代码
    
    // 解锁
    pthread_mutex_unlock(&lock);
    // 销毁锁
    pthread_mutex_destroy(&lock);
    pthread_mutexattr_destroy(&attr);
    
}

pthread_rwlock<read-write lock>

  • Need to import framework<pthread.h>
  • Read-write lock. When operating on a file, the write operation is exclusive. Once multiple threads write to the same file, the consequences are immeasurable, but reading is possible, and there is no problem when multiple threads read .
  • When the read-write lock is occupied by a thread in read mode, other threads of the write operation will be blocked, and other threads of the read operation can continue.
  • When a read-write lock is occupied by a thread in write mode, other threads for write operations are blocked, and other threads for read operations are also blocked.
  • Code example:
    // 初始化
    pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
    // 写模式
    pthread_rwlock_wrlock(&rwlock);
    // 读模式
    pthread_rwlock_rdlock(&rwlock);
    // 读模式或者写模式的解锁
    pthread_rwlock_unlock(&rwlock);
    // 销毁锁
    pthread_rwlock_destroy(&rwlock);
#import <pthread.h>
@interface ThreadSafeViewController (){
    pthread_rwlock_t rwLock;
}

- (void)threadSafePthread_rwlock{
    rwLock = (pthread_rwlock_t)PTHREAD_RWLOCK_INITIALIZER;
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self readBookWithTag:1];
    });
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self readBookWithTag:2];
    });
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self writeBook:3];
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self writeBook:4];
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self readBookWithTag:5];
    });
    
}
// 读
- (void)readBookWithTag:(NSInteger )tag {
    pthread_rwlock_rdlock(&rwLock);
    
    NSString *redStr = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"1" ofType:@"txt"] encoding:NSUTF8StringEncoding error:nil];
    NSLog(@"读:%ld -- %@",tag,redStr);
    
    pthread_rwlock_unlock(&rwLock);
}
// 写
- (void)writeBook:(NSInteger)tag {
    pthread_rwlock_wrlock(&rwLock);

    NSLog(@"写入文件数据 tag == %ld",tag);
    NSString *writeStr = [NSString stringWithFormat:@"tag值为:%ld",tag];
    [writeStr writeToFile:[[NSBundle mainBundle] pathForResource:@"1" ofType:@"txt"] atomically:NO encoding:NSUTF8StringEncoding error:nil];
    NSString *redStr = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"1" ofType:@"txt"] encoding:NSUTF8StringEncoding error:nil];
    NSLog(@"写入之后读取文件数据:%@",redStr);

    pthread_rwlock_unlock(&rwLock);
}
/*
输出结果
2017-08-25 17:31:34.254 Multi-threaded[10198:602785] 读:2 -- 文件初始内容
2017-08-25 17:31:34.254 Multi-threaded[10198:602768] 读:1 -- 文件初始内容
2017-08-25 17:31:34.255 Multi-threaded[10198:602771] 写入文件数据 tag == 3
2017-08-25 17:31:34.256 Multi-threaded[10198:602771] 写入之后读取文件数据:tag值为:3
2017-08-25 17:31:45.944 Multi-threaded[10198:602787] 写入文件数据 tag == 4
2017-08-25 17:31:45.946 Multi-threaded[10198:602787] 写入之后读取文件数据:tag值为:4
2017-08-25 17:31:45.947 Multi-threaded[10198:602769] 读:5 -- tag值为:4
*/

POSIX Conditions<condition lock>

  • POSIX conditional locks need a mutex and a condition to implement. Although it seems to have nothing to do with it, at runtime, the mutex will be combined with the condition. The thread will be woken up by a signal combining mutex and condition.
  • First initialize the condition and the mutex. When ready_to_gois flase, enter the loop, and then the thread will be suspended until another thread ready_to_gosets true, and when the signal is sent, the thread will be woken up.
  • Need to import framework <pthread.h>.
  • Code example:
#import <pthread.h>
@interface ThreadSafeViewController (){
    pthread_mutex_t mutex;
    pthread_cond_t condition;
    Boolean     ready_to_go;
}

- (void)threadSafePOSIXConditions{
    // 条件是否满足标识符,true代表满足
//    ready_to_go = true;
    // 初始化锁
    mutex = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER;
    pthread_mutex_init(&mutex, NULL);
    // 初始化条件
    pthread_cond_init(&condition, NULL);
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self MyWaitOnConditionFunction];
    });
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self SignalThreadUsingCondition];
    });
}
- (void)MyWaitOnConditionFunction{
    // 加锁
    pthread_mutex_lock(&mutex);
    // 判断运行条件,如果运行条件不满足,则该线程等待,等待其它线程运行结束,并发出等待信号
    while(ready_to_go == false){
        // 条件不满足,发出线程等待信号
        NSLog(@"MyWait:线程等待");
        pthread_cond_wait(&condition, &mutex);
    }
    NSLog(@"MyWait:开始处理本线程数据");
    ready_to_go = false;
    NSLog(@"MyWait:本线程数据处理结束");
    // 解锁
    pthread_mutex_unlock(&mutex);
}
- (void)SignalThreadUsingCondition{
    pthread_mutex_lock(&mutex);
    NSLog(@"Signal:开始本线程数据处理等操作");
    ready_to_go = true;
    NSLog(@"Signal:本线程数据处理结束");
    // 发送信号让其它线程运行
    pthread_cond_signal(&condition);
    // 解锁
    pthread_mutex_unlock(&mutex);
}
/*
运行结果 ready_to_go 初始值为false
	2017-08-25 22:04:26.945 Multi-threaded[10702:645021] MyWait:线程等待
	2017-08-25 22:04:26.947 Multi-threaded[10702:645022] Signal:开始本线程数据处理等操作
	2017-08-25 22:04:26.948 Multi-threaded[10702:645022] Signal:本线程数据处理结束
	2017-08-25 22:04:26.948 Multi-threaded[10702:645021] MyWait:开始处理本线程数据
	2017-08-25 22:04:26.949 Multi-threaded[10702:645021] MyWait:本线程数据处理结束
运行结果 ready_to_go 初始值为true
	2017-08-25 22:06:24.666 Multi-threaded[10786:647623] MyWait:开始处理本线程数据
	2017-08-25 22:06:24.666 Multi-threaded[10786:647623] MyWait:本线程数据处理结束
	2017-08-25 22:06:24.666 Multi-threaded[10786:647624] Signal:开始本线程数据处理等操作
	2017-08-25 22:06:24.667 Multi-threaded[10786:647624] Signal:本线程数据处理结束

*/

OSSpinLock<spin lock>

  • Spin lock: Similar to a mutex, it is a lock to ensure thread safety.
  • For a mutex, when a thread acquires the lock, other threads that want to acquire the lock will be blocked until the lock is released.
  • For a spin lock, when a thread acquires the lock, other threads will keep looping to see if the lock is released. Therefore, this lock is more suitable for situations where the lock holder's storage time is short.
  • This spin lock has a priority inversion problem, refer to: OSSpinLock that is no longer safe
  • After iOS 10.0that, Apple came up with a whole os_unfair_lock_tand used it to solve the priority inversion problem.
  • Frameworks need to be imported <libkern/OSAtomic.h>.
  • Code example:
#import <libkern/OSAtomic.h>
- (void)threadSafeOSSpinLock{
//    // 初始化
//    OSSpinLock spinLock = OS_SPINLOCK_INIT;
    // 加锁
//    OSSpinLockLock(&spinLock);
    // 解锁
//    OSSpinLockUnlock(&spinLock);
    // 使用示例:
    __block OSSpinLock oslock = OS_SPINLOCK_INIT;
    //线程1
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"线程1 准备上锁");
        OSSpinLockLock(&oslock);
        sleep(4);
        NSLog(@"线程1 正在处理数据");
        OSSpinLockUnlock(&oslock);
        NSLog(@"线程1 解锁成功");
    });
    
    //线程2
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"线程2 准备上锁");
        OSSpinLockLock(&oslock);
        NSLog(@"线程2 正在处理数据");
        OSSpinLockUnlock(&oslock);
        NSLog(@"线程2 解锁成功");
    });
}
// 根据警告可以发现,在iOS10以后将会被os_unfair_lock代替
/*
2017-08-25 23:18:26.345 Multi-threaded[11874:710484] 线程安全主线程打印
2017-08-25 23:18:26.346 Multi-threaded[11874:710569] 线程1 准备上锁
2017-08-25 23:18:26.347 Multi-threaded[11874:710570] 线程2 准备上锁
2017-08-25 23:18:30.352 Multi-threaded[11874:710569] 线程1 正在处理数据
2017-08-25 23:18:30.353 Multi-threaded[11874:710569] 线程1 解锁成功
2017-08-25 23:18:30.435 Multi-threaded[11874:710570] 线程2 正在处理数据
2017-08-25 23:18:30.435 Multi-threaded[11874:710570] 线程2 解锁成功
*/

os_unfair_lock <spin lock>

  • spin lock.
  • Apple came up with it and used it to solve OSSpinLockthe priority inversion problem.
  • Frameworks need to be imported <os/lock.h>.
  • Code example:
#import <os/lock.h>
- (void)threadSafeOs_unfair_lock{
//    // 初始化
//    os_unfair_lock_t unfairLock = &(OS_UNFAIR_LOCK_INIT);
//    // 加锁
//    os_unfair_lock_lock(unfairLock);
//    // 解锁
//    os_unfair_lock_unlock(unfairLock);
//    // 尝试加锁  如果返回YES表示已经加锁
//    BOOL isLock = os_unfair_lock_trylock(unfairLock);
}


Semaphore mechanism

  • A signal mechanism is provided in GCD, which can also solve the problem of resource preemption (different from the synchronization lock mechanism)
  • The semaphore in GCD is of dispatch_semaphore_t type, which supports signal notification and signal waiting.
  • Whenever a signal notification is sent, the semaphore +1; whenever a waiting signal is sent, the semaphore -1; if the semaphore is 0, the signal will be in a waiting state until the semaphore is greater than 0 to start execution.
  • According to this principle, we can initialize a semaphore variable. The default semaphore is set to 1. Whenever a thread enters the "lock code", it will call the signal waiting command (the semaphore is 0 at this time) to start waiting. At this time, other threads cannot Enter, send a signal notification after execution (the semaphore is 1 at this time), and other threads start to execute, so that the purpose of thread synchronization is achieved.
  • Code example:
// 如果利用信号量进行线程的加解锁,信号量初始值应当设置为1,这里为了测试,设置的值为3,orig为3
    dispatch_semaphore_t semapore_t = dispatch_semaphore_create(3); // value = 3,orig = 3
    
    // 发出等待信号,信号量-1,value值发生改变,value为0,加锁
    dispatch_semaphore_wait(semapore_t, DISPATCH_TIME_FOREVER); // value = 2,orig = 3
    
    // 发出信号,信号量+1,value值发生改变,解锁
    dispatch_semaphore_signal(semapore_t); // value = 3,orig = 3

  • If there is no problem of priority inversion, osspinlockthe possession is absolute, followed by dispatch_semaphore,dispatch_semaphorea os_unfair_locksmall gap with it, and the second is pthread_mutex.
  • In fact, during the test, the performance and the number of times are related. That is to say, these types of locks will perform best in different situations. When the number of times is large, the performance ranking is the same as above. So if you use it in a project, you can choose it according to the project situation.
  • When reading and writing files, pthread_rwlockit is better . File reading and writing usually consumes a lot of resources, and when using a mutex to read files at the same time, it will block other file-reading threads, but pthread_rwlockwill not.
  • When the performance requirements are high, you can use pthread_mutexor dispath_semaphore, because OSSpinLockcannot guarantee thread safety very well, but only iOS10in os_unfair_lock, so the first two are better choices. It can not only guarantee speed, but also ensure thread safety.
  • For NSLockand its subclass, Velocity NSLock < NSCondition < NSRecursiveLock < NSConditionLock.

Code address:
https://github.com/FlyingKuiKui/Multi-threaded.git

Guess you like

Origin blog.csdn.net/FlyingKuiKui/article/details/77547647#comments_25022438