Multithreading security risks

  • Resource sharing

    • A resource may be shared by multiple threads, that is, multiple threads may access the same resource
    • For example, multiple threads access the same object, the same variable, the same file
  • When multiple threads access the same resource, data corruption and data security issues can easily occur

Multi-threaded security example 01 – Saving and withdrawing money

  • Case analysis
    • 1. Time order saving thread read amount 1000,
    • 2. The deposit thread also reads the amount 1000 before the deposit thread
    • 3. The depositing thread deposits 1000. The depositing thread displays 2000
    • 4. Withdrawal amount Withdraw 500 shows the amount is 500
    • 5. The value is 500

Multithreaded security example 02 – selling tickets

  • Case code
#import "ViewController.h" @interface ViewController () @property (assign, nonatomic) int ticketsCount; @property (assign, nonatomic) int money; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. [self moneyTest]; } / / - (void)moneyTest {self. Money = 100; dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_async(queue, ^{ for (int i = 0; i < 10; i++) { [self saveMoney]; }}); dispatch_async(queue, ^{ for (int i = 0; i < 10; i++) { [self drawMoney]; }}); } /** saveMoney */ - (void)saveMoney {int oldMoney = self.money; sleep(.2); oldMoney += 50; self.money = oldMoney; NSLog(@" save 50, remaining % ddollar - %@", oldMoney, [NSThread currentThread]); } /** drawMoney {int oldMoney = self. drawMoney; sleep(.2); oldMoney -= 20; self.money = oldMoney; NSLog(@" take 20, remaining %d - %@", oldMoney, [NSThread currentThread]); } /** sell 1 ticket */ - (void)saleTicket {int oldTicketsCount = self.ticketsCount; sleep(.2); oldTicketsCount--; self.ticketsCount = oldTicketsCount; NSLog(@" %d tickets left - %@", oldTicketsCount, [NSThread currentThread]); */ - (void)ticketTest {self. TicketsCount = 15; dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_async(queue, ^{ for (int i = 0; i < 5; i++) { [self saleTicket]; }}); dispatch_async(queue, ^{ for (int i = 0; i < 5; i++) { [self saleTicket]; }}); dispatch_async(queue, ^{ for (int i = 0; i < 5; i++) { [self saleTicket]; }}); } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { }Copy the code

Multithreading security risk analysis

  • Solution: Use thread synchronization technology (synchronization, or coordinated pacing, in a predetermined order)
  • A common thread synchronization technique is locking

  • When thread A accesses the Integer, the lock is unlocked
  • Thread B accesses the lock and unlocks it when it is done.

Thread synchronization scheme in iOS

  • OSSpinLock
  • os_unfair_lock
  • pthread_mutex
  • dispatch_semaphore
  • Dispatch_queue (DISPATCH_QUEUE_SERIAL) // Serial queue
  • NSLock
  • NSRecursiveLock
  • NSCondition
  • NSConditionLock

@synchronized

OSSpinLock

  • OSSpinLock is called a “spin lock”, and the thread waiting for the lock will be in busy-wait state, occupying CPU resources
  • It is no longer secure and priority inversion may occur
  • If the thread waiting for the lock has a higher priority, it will continue to occupy CPU resources, and the lower priority thread will not be able to release the lock
  • #import #import

  • conclusion
    • Spin lock: When a thread locks, if the lock is already locked by another thread, it is waiting for the lock to be unlocked. Internally doing a while loop consumes CPU resources.
    • Simple initialization lock and unlock, different threads must use the same lock.
  • Priority inversion can occur.
    • Assume that thread 1 has a higher priority than thread 2.
    • But to start the task, we go to thread 2, thread 2 locks,
    • Thread 1 starts the task and waits for thread 2 to unlock it.
    • In this case, thread 1 has a higher priority than thread 2, so thread 2 needs to complete thread 1 first, but thread 1 is waiting for thread 2 to unlock.
    • The program eventually deadlocks.

os_unfair_lock

  • Os_unfair_lock os_unfair_lock is used to replace insecure OSSpinLock. It is only supported in iOS10
  • From underlying calls, threads waiting for os_UNFAIR_LOCK are dormant, not busy, etc
  • #import < OS /lock.h>

  • conclusion
    • Mutexes replace spinlocks. Usage same as above, simple add lock unlock.

pthread_mutex

  • Mutex is called a mutex, and the thread waiting for the lock will go to sleep. Mutex can also cause deadlocks.
  • #import

  • conclusion
    • Pthread_mutex_init (mutex,NULL
    • You can add attributes, but you need to initialize them before adding them, and then destroy them
    • Last destroy lock

Pthread_mutex – Recursive lock

  • // Recursive locking: allow the same thread to lock the same lock repeatedly, must be the same thread.

- (void)otherTest
{
    pthread_mutex_lock(&_mutex);
    
    NSLog(@"%s", __func__);
    
    static int count = 0;
    if (count < 10) {
        count++;
        [self otherTest];
    }
    
    pthread_mutex_unlock(&_mutex);
}

Copy the code
  • conclusion
    • When a recursive function is called, it enters the method again before unlocking, causing a deadlock.
    • PTHREAD_MUTEX_RECURSIVE is passed when the lock is created
    • Recursive locking: Allows the same thread to lock the same lock repeatedly.
    Thread 1: otherTest (+-) otherTest (+-) otherTest (+-) Thread 2: otherTest (wait)Copy the code
    • Thread 2 does not start until thread 1’s recursive methods have been called.

Pthread_mutexes – conditions

  • / / wait for
    • pthread_cond_wait(&_cond, &_mutex);
  • / / signal
    • pthread_cond_signal(&_cond);
  • conclusion
    • Pthread_cond_init (&_cond, NULL);
    • Thread 1 starts the task first, and thread 1 locks it. Thread 2 waits for thread 1 to unlock the task and then locks it to execute the task.
    • Pthread_cond_wait (&_cond, &_mutex); pthread_cond_wait(&_cond, &_mutex);
    • pthread_cond_wait(&_cond, &_mutex); The lock of thread 1 is unlocked first and goes to sleep.
    • Pthread_cond_signal (&_cond); pthread_cond_signal(&_cond);
    • Thread 1 receives the signal and waits for thread 2 to unlock the lock. When thread 2 finishes unlocking the lock, thread 1 locks and performs the following tasks.

NSLock, NSRecursiveLock

  • NSLock is a wrapper around a mutex normal lock

  • NSRecursiveLock encapsulates a mutex recursive lock, and has the same API as NSLock
  • conclusion
    • The encapsulation of the above method is convenient and does not need to be very cumbersome to create.
    self.ticketLock = [[NSLock alloc] init]; // Create lock [self.ticketLock lock]; [self.ticketLock unlock]; / / unlockCopy the code
    • NSRecursiveLock encapsulates a recursive lock.

NSCondition

  • NSCondition is the encapsulation of mutex and COND

  • conclusion
    • Encapsulation of conditional locks does not require initialization conditions
    @property (strong, nonatomic) NSCondition *condition;
    self.condition = [[NSCondition alloc] init];
    [self.condition lock];
    [self.condition wait];
    [self.condition signal];
    [self.condition unlock];
    Copy the code

NSConditionLock

  • NSConditionLock is a further encapsulation of NSCondition, and you can set specific conditional values

self.conditionLock = [[NSConditionLock alloc] initWithCondition:1];

- (void)__one
{
    [self.conditionLock lock];
    
    NSLog(@"__one");
    sleep(1);
    
    [self.conditionLock unlockWithCondition:2];
}

- (void)__two
{
    [self.conditionLock lockWhenCondition:2];
    
    NSLog(@"__two");
    sleep(1);
    
    [self.conditionLock unlockWithCondition:3];
}

- (void)__three
{
    [self.conditionLock lockWhenCondition:3];
    
    NSLog(@"__three");
    
    [self.conditionLock unlock];
}
Copy the code
  • conclusion
    • Initialize condition 1, and lock if the condition is met
    • 2 The waiting condition is 2. 3 The waiting condition is 3.
    • 1 Unlock the account after the command is executed. Set condition to 2
    • Condition 2 is locked, and condition 3 is set after unlocking
    • .
    • To ensure that the thread is executed step by step, perform 1->2->3
  • Condition and conditionLock
    • Condition: 1 unlocks sleep in the middle of execution. 2 sends a notification to 1 after execution. 1 is awakened and continues execution
    • ConditionLock, you can only be called up and executed if the conditions are met.

dispatch_semaphore

  • A semaphore is called a semaphore.
  • The initial value of a semaphore that can be used to control the maximum number of concurrent accesses by a thread
  • The initial value of the semaphore is 1, which means that only one thread is allowed to access the resource at the same time to ensure thread synchronization

- (instancetype)init { if (self = [super init]) { self.semaphore = dispatch_semaphore_create(5); self.ticketSemaphore = dispatch_semaphore_create(1); self.moneySemaphore = dispatch_semaphore_create(1); } return self; } - (void)__drawMoney { dispatch_semaphore_wait(self.moneySemaphore, DISPATCH_TIME_FOREVER); [super __drawMoney]; dispatch_semaphore_signal(self.moneySemaphore); } - (void)__saveMoney { dispatch_semaphore_wait(self.moneySemaphore, DISPATCH_TIME_FOREVER); [super __saveMoney]; dispatch_semaphore_signal(self.moneySemaphore); } - (void)__saleTicket { dispatch_semaphore_wait(self.ticketSemaphore, DISPATCH_TIME_FOREVER); [super __saleTicket]; dispatch_semaphore_signal(self.ticketSemaphore); } - (void)otherTest { for (int i = 0; i < 20; i++) { [[[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil] start]; } // Thread 10, 7, 6, 9, 8 - (void)test {// Thread 10, 7, 6, 9, 8 - (void)test {// Thread 10, 7, 6, 9, 8 - (void)test; Then proceed to dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER); sleep(2); NSLog(@"test - %@", [NSThread currentThread]); // dispatch_semaphore_signal(self.semaphore); }Copy the code
  • conclusion
    • Similar to the serial queue below, the number of concurrent connections is set to 1 and only one thread accesses the resource
    • self.semaphore = dispatch_semaphore_create(5); This one starts with five concurrent threads one, two, three, four, five.
    • DISPATCH_TIME_FOREVER forever
    • dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER); This internal will execute the command while the semaphore -1, when the semaphore <=0 value enters the wait, the wait time is controlled by parameters. So this is going to open up five threads to do the task, and if none of the five threads have finished the task, it’s going to go to sleep and wait at this point. So thread 6 will not be opened.
    • dispatch_semaphore_signal(self.semaphore); +1, then dispatch_semaphore_wait is awakened, at which point thread 1 completes its task and is destroyed. Open thread 6. This is the working thread is thread 2, 3, 4, 5, 6. Still 5 threads working.

dispatch_queue

  • It is also possible to achieve thread synchronization using serial queues directly from GCD

  • The serial queue cannot start new threads, which means that only one person can do the tasks, ensuring that the tasks are executed in sequence

@synchronized

  • @synchronized is the encapsulation of a mutex recursive lock
  • Objc-sync.mm file in objC4
  • @synchronized(obj) internally generates a recursive lock corresponding to OBj, and then locks and unlocks the lock

  • Pass in an object and create a mutex on it.
  • Add two methods that control the same object and use the same object

  • You can create a static variable to lock the same object.

  • Worst performance is not recommended.

Performance comparison of iOS thread synchronization schemes is for reference only

  • Sort performance from highest to lowest
  • Os_unfair_lock os_UNfair_lock
  • OSSpinLock
  • dispatch_semaphore
  • pthread_mutex
  • dispatch_queue(DISPATCH_QUEUE_SERIAL)
  • NSLock
  • NSCondition
  • Pthread_mutex (recursive) // The recursive lock guarantees the invocation of recursive methods.
  • NSRecursiveLock
  • NSConditionLock
  • @synchronized

Spin lock and mutex comparison

  • When is it cost-effective to use a spin lock?

    • Threads are expected to wait a short time for locks
    • Locking code (critical sections) is often invoked, but contention rarely occurs
    • CPU resources are not tight
    • Multicore processor
  • When is it cost-effective to use a mutex?

    • The thread is expected to wait a long time for the lock
    • Single-core processor
    • There are IO operations in the critical area
    • Critical section code complex or large loop
    • Critical sections are very competitive