This is the 25th day of my participation in the August Challenge

1. Lock type

Spin lock: The thread repeatedly checks that the lock variable is available. Because the thread keeps executing during this process, it is a busy wait. Once a spin lock is acquired, the thread holds it until it explicitly releases the spin lock. Spin-locks avoid the scheduling overhead of the process context and are therefore effective in situations where threads block only for short periods of time.

Mutex: A mechanism used in multithreaded programming to prevent two threads from reading or writing to the same common resource (such as a global variable) at the same time. When one thread is working on a task, no other thread can work on the task. This is mutually exclusive. Other threads execute in a certain order after this thread completes, which is called synchronization. This is done by slicing code into critical sections one by one, with mutexes classified as recursive and non-recursive. The mutexes are:

  • NSLock
  • pthread_mutex
  • @synchronized

Conditional locks: Conditional variables that sleep when certain resource requirements of a process are not met, i.e. locked. When the resource is allocated, the conditional lock is opened and the process continues

  • NSCondition
  • NSConditionLock

Recursive locking: The same thread can be locked N times without causing a deadlock

  • NSRecursiveLock
  • pthread_mutex(recursive)

Semaphore: a more advanced synchronization mechanism. Mutex is a special case of semaphore with a value of only 0/1. Semaphores can have more value space for more complex synchronization, rather than just mutual exclusion between threads.

  • dispatch_semaphore

In fact, the basic lock includes three types of spin lock mutex read and write lock, other such as conditional lock, recursive lock, semaphore is the upper packaging and implementation!

2. Lock application

This code is going to crash when it runs, and the main reason it’s going to crash is because testArray at some point becomes nil.

If the following code is executed, there will be 10 testMethods (10) executing asynchronously, so how to ensure that they are executed one after the other? How do you lock it?

TestMethod (10) can also be locked after the testMethod declaration. You cannot lock the testMethod block, otherwise you would recursively lock it, and NSLock does not support recursively lock.

So if I have a recursive lock, can I do this? And the answer is no, because this is a multithreaded environment, and recursive locks don’t do multithreaded recursion. To multithread, recursively, you need to use @synchronized locks.

Here you can see that the @synchronized lock works fine.

3. The underlying implementation of locking

See NSLock, which is in compliance with the NSLocking protocol, in the foundation framework.

The underlying implementation of NSLock is the encapsulation of pthread_mutex. Lock unlocking is also a call to the Pthread_mutex related API.

When you see the Recursive lock NSRecursiveLock, you see that there is an extra call to pthread_mutexattr_setTYPE initialized with type set to Recursive.

NSCondition adds the condition handler.

NSConditionLock’s exploration

Explore this in conjunction with NSConditionLock. What is the result of the following execution? The result of this execution is that the 3 is out of order, but the 2 comes before the 1. NSConditionLock (condition = 2) conditionlock (condition = 2) conditionlock (condition = 2) conditionlock (condition = 2) Thread 3 because there is no condition, so just ordinary lock unlock, direct execution.

So there are a couple of problems here.

  • What’s the difference between NSConditionLock and NSCondition
  • What is the parameter 2 of the initialization pass
  • How are lockwhenconditions controlled
  • So what does lock with condition do

4.1 [NSConditionLock initWithCondition:]

Run after hitting a breakpoint. NSConditionLock initWithCondition

The next step is to assemble the initWithCondition method.

Then I read the registers and found that everything matched.

Then in the assembly, put all bl breakpoints, because BL is where to jump. After the break point, continue to run to the first BL.

So we read the register, we find out that it’s an object that we don’t know and we call an init method with an argument of 2.

As you go down, you see that this is an NSConditionLock object calling the Init method. The parameter is 2.

Moving on, you see that this is an NSConditionLock object calling the zone method. The parameter is 2.

As YOU go down, you see that there’s an NSCondition calling allocWithZone, and the arguments are gone

And as you go down, you see that this is an NSCondition object calling init.

Further down, return, which is the return value of the method [NSConditionLock initWithCondition :]. It returns NSConditionLock.

X /8gx displays the NSCondition object as a member variable of NSConditionLock. The NSCondition object here is the one that was created earlier. The previous parameter 2 is also a member variable of the NSConditionLock object.

NSConditionLock encapsulates an NSCondition and preserves the prequel 2.

  1. ? Object calls init.
  2. [NSConditionLock init: 2)
  3. [NSConditionLock zone: 2)
  4. [NSCondition allocWithZone]
  5. [NSCondition init]

4.2 [NSConditionLock lockWhenCondition:]

Then lockWhenCondition. Next [NSConditionLock lockWhenCondition:] break point, run to assemble after bl hit the break point.

Read the register and find an NSDate object calling the distantFuture method.

Continue to go down after reading the register, found is a NSConditionLock object call lockWhenCondition: beforeDate: method. I’m going to read an extra x3 because it’s two arguments, x3 feels like the return value of the distantFuture method.

Continue down [NSConditionLock lockWhenCondition: beforeDate:] symbols breakpoints, enter to jump after laying a breakpoint. There’s nothing at 8, and then it goes down to 13.

When you read the register, you see that the NSCondition object here calls a lock.

Jump down, find nothing, keep going down.

Here the register is read and the NSCondition object calls a waitUntilDate method. Parameters for [NSConditionLock lockWhenCondition: beforeDate:] incoming parameters.

And then jump to another thread [NSConditionLock lockWhenCondition: beforeDate:], because here is lock doesn’t work. Jump over and go to the next step of that step, and there’s nothing there.

Moving on, you see that an NSCondition object’s UNLOCK method is called.

I keep going down to return, and I find that I return 1.

[NSConditionLock lockWhenCondition:]

  1. [NSDate distantFuture:];
  2. [NSConditionLock lockWhenCondition: beforeDate:] 2.1 [NSCondition lock]; [NSCondition waitUntilDate:] 2.4 return 1

4.3 [NSConditionLock unlockWithCondition:]

And then if you go down, you’re going to be in the unlockWithCondition method, you’re going to come in, you’re going to set a breakpoint, and then you’re going down.

Read the register and find that the NSCondition object calls the lock method.

Read the register further down and see that the NSCondition object calls the broadcast method.

Moving on, you see that an NSCondition object’s UNLOCK method is called.

[NSConditionLock unlockWithCondition :]

  1. [NSCondition lock];
  2. [NSCondition broadcast];
  3. [NSCondition unlock];

So now that we’ve done unlockWithCondition, we go back to the previous thread, and we’ve done unlock.

And then the value of return is also 1.

And then you go down here.

In other words, the execution order here is:

  1. [conditionLock lockWhenCondition:1];
  2. [conditionLock lockWhenCondition:2];
  3. [conditionLock unlockWithCondition:1];
  4. [conditionLock unlockWithCondition:0];

5. Underlying source code

Let’s see if the underlying source code is implemented this way. First of all, init, there really are two properties, one _cond = NSCondition() and one _value: Int. And at initialization, _value is assigned to the passed parameter condition. And _cond is initialized.

Next, NSConditionLock lockWhenCondition:]. Find here did call Date. DistantFuture and [NSConditionLock lockWhenCondition: beforeDate:]

Next to [NSConditionLock lockWhenCondition: beforeDate:], found that _cond of lock and Unlock methods, namely the lock] [NSCondition; And [NSCondition unlock]; And when _thread! = nil || _value ! Condition is called! _cond. Wait (until: limit). Time out to unlock, return false. Otherwise, it is unlocked normally and returns true.

[NSConditionLock unlockWithCondition :] [NSConditionLock unlockWithCondition :] [NSConditionLock unlockWithCondition :]]

6. Read/write locks

In addition to mutex and spin locks, another lock is the read/write lock.

A read/write lock is a special mutex lock that divides visitors to a shared resource into readers and writers. Readers only read the shared resource, while writers write the shared resource. This type of lock improves concurrency over a spin lock because in a multiprocessor system it allows multiple readers to access a shared resource at the same time, with the maximum possible number of readers being the actual number of logical cpus. Writers are exclusive; a read/write lock can have only one writer or more readers at a time (depending on the number of cpus), but not both readers and writers. Preemption also fails during read/write lock holding. If the read-write lock currently has no reader and no writer, then the writer can acquire the read-write lock immediately, otherwise it must spin there until there are no writers or readers. If the read-write lock has no writer, the reader can acquire the read-write lock immediately, otherwise the reader must spin there until the writer releases the read-write lock. To implement a read/write lock you need to:

  • Read write
  • Write and write are mutually exclusive
  • Read and write the mutex
  • Write cannot block task execution

The key to implementing read/write locks with GCD is the fence function. Here the setter uses barrier_async without blocking the execution of another thread, and waits for the previous operation to complete before executing, and ensures single writes. Sync is used for reading to ensure that the value is returned later, and because it is multithreaded, it can be multi-read. Sync ensures that reads and writes are mutually exclusive.