Where is the iOS multithreading in the end unsafe?

Source: Internet
Author: User

The concept of multi-threaded security in iOS is met in many places, why unsafe, unsafe and how to define, in fact, is a topic worth probing.

Shared state, multi-threaded common access to the property of an object, in iOS programming is a very common use of scenes, we are from the property of multi-threaded security.

Property

When we discuss the property multithreading security, many people know that the property plus atomic attribute, can be a certain degree of security of multithreading, similar:

@property (Atomic, strong) nsstring* UserName;
Things do not seem so simple, to analyze the performance of the property in multi-threaded scenarios, you need to first distinguish the type of property.

We can simply divide the property into value type and object type, the value type refers to the primitive type, including int, long, bool and other non-object types, the other is the object type, declared as a pointer, can point to an area of memory that conforms to the type definition.

The username in the code above is obviously an object type, and when we visit username, it is possible that the username itself, or the area of memory that the username points to.

Like what:

Self.username = @ "Peak";
is to assign a value to the pointer itself. and

[Self.username rangeofstring:@ "Peak"];
is in the memory area where the string that the pointer is pointing to is located, which is not the same.

So we can basically divide the property into three categories:

After the class is divided, we need to understand the memory models of these three types of property.

Memory Layout

When we discuss multithreading security, we are actually talking about the security of multiple threads accessing a memory area at the same time. For the same area, we have two operations, read (load) and write (store), read and write simultaneously in the same area, there is the possibility of multithreading is unsafe. So before we start the discussion, we need to understand the memory models of the above three property, as shown here:

In the case of a 64-bit system, the pointer nsstring* is a 8-byte memory area, and int count is a 4-byte area, and @ "Peak" is an area of memory based on the length of the string.

When we access the property, we are actually accessing three memory areas.

Self.username = @ "Peak";
is to modify the first area.

Self.count = 10;
is to modify the second block area.

[Self.username rangeofstring:@ "Peak"];
is to read the third area.

Unsafe definitions

Having understood the type of property and their corresponding memory model, let's look at the unsafe definition. Wikipedia says:

A piece of code is thread-safe if it manipulates shared data structures only in a manner the guarantees safe execution by Multiple threads at the same time
This definition still looks a bit abstract, and we can interpret multithreading insecurity as a result of unexpected results in multithreaded access. This unexpected result contains several scenarios, not necessarily refers to crash, followed by analysis.

First look at how multithreading is accessing memory at the same time. Memory access can be expressed without regard to the cache of variables cached by the CPU cache:

As can be seen, we have only one address bus, one memory. Even in multi-threaded environments, it is impossible to have two threads simultaneously accessing the same piece of memory area of the scene, memory access must be through a address bus serial queue access, so before continuing to follow, we must first clear a few conclusions:

Conclusion One: the memory is accessed serially, and does not result in the confusion of memory data or the crash of the application.

Conclusion Two: If the memory length of the read-write (load or store) is less than or equal to the length of address bus, then the read-write operation is atomic and completes once. For example, Bool,int,long in a 64-bit system of a single read and write are atomic operations.

Next we look at the multi-threaded insecure scenarios based on the classification of the above three property categories.

Value Type Property

First take the BOOL value type as an example, when we have two threads accessing the following properties:

@property (nonatomic, Assgin) BOOL isDeleted;

Thread 1
bool isDeleted = self.isdeleted;

Thread 2
self.isdeleted = false;
Thread 1 and Thread 2, one read (load), one write (store), access to bool isdeleted may have a succession of points, but must be serial queuing. And since the bool size is only 1 bytes, the address bus for the 64-bit system can support 8 bytes of length for read-write instructions, so we can think of the reading and writing of BOOL as atomic, so when we declare a property of type bool, from an atomic point of view, There is no real difference between using atomic and nonatomic (of course if the Getter method is overloaded).

What if it is of type int?

@property (nonatomic, assgin) int count;

Thread 1
int curcount = Self.count;

Thread 2
Self.count = 1;
The same type of int is 4 bytes long, and both read and write can be done with a single instruction, so the theory of read and write operations is atomic. There is no difference between nonatomic and atomic in terms of accessing memory.

What's the use of atomic? As far as I know, there are two uses:

Use one: Generate getter and setter for atomic operations.

After setting atomic, the default generated getter and setter method execution is atomic. That is, when we execute the Getter method in thread 1 (Create the call stack, return address, out of the stack), if you want to execute the setter method, B must wait for the getter method to complete before executing. For example, in a 32-bit system, if you return a 64-bit double with a getter, the address bus width is 32 bits, when you read a double from memory, it cannot be done by atomic operation, if you do not lock by Atomic, It is possible for the setter operation to occur on other threads in the middle of the read, resulting in an exception value. If this outlier occurs, multithreading is unsafe.

Use two: Set memory Barrier

For the implementation of Objective C, almost all locking operations will eventually set the memory barrier,atomic is essentially a lock on the getter,setter, so the memory barrier will also be set. The official documents are stated as follows:

Note:most types of locks also incorporate a memory barrier to ensure so any preceding load and store instructions is C Ompleted before entering the critical section.
What is the use of memory barrier?

Memory barrier is able to guarantee the order in which the operations are done, in the order in which our code is written. It sounds a bit strange, the fact is that the compiler optimizes our code to change the sequence of machine instructions that our code ultimately translates into a scenario that it deems reasonable. This means the following code:

Self.inta = 0; Line 1
SELF.INTB = 1; Line 2
The compiler might execute line2 in some scenarios and then execute line1, because it considers that there is no dependency between A and B, although there is a dependency on another thread, Inta and INTB, in the execution of the code, and line1 must be required to execute before line2.

If the property is set to atomic, that is, after the memory barrier is set, it is guaranteed that the line1 execution must precede the line2, of course, this scenario is very rare, one is the variable cross-thread access dependency, the second is the compiler optimization, Two conditions are indispensable. This extreme scenario, atomic really can make our code more multithreaded security point, but I write iOS code so far, have not encountered this scenario, the larger possibility is that the compiler is smart enough to set the memory barrier where we need it.

Is the use of atomic on a certain multithreading security? We can look at the following code:

@property (Atomic, assign) int intA;

Thread A
for (int i = 0; i < 10000; i + +) {
Self.inta = Self.inta + 1;
NSLog (@ "Thread A:%d\n", Self.inta);
}

Thread B
for (int i = 0; i < 10000; i + +) {
Self.inta = Self.inta + 1;
NSLog (@ "Thread B:%d\n", Self.inta);
}
Even if I declare inta as atomic, the final result will not necessarily be 20000. The reason is because Self.inta = Self.inta + 1; not atomic operations, although the getter and setter of Inta is atomic, but when we use the INTA, the entire statement is not atomic, the line assignment code contains at least read (load), +1 ( Add), Assignment (store) Three-step operation, the current thread store may have been executed several times by other threads store, resulting in a value less than the expected value. This scenario can also be called multithreading insecure.

Pointer property

The pointer property generally points to an object, such as:

@property (Atomic, strong) nsstring* UserName;
Whether the iOS system is a 32-bit system or 64-bit, the value of a pointer can be completed by a command load or store. However, unlike primitive type, the object type also has memory management related operations. In the MRC era, the default setter generated by the system is similar to the following:

-(void) Setusername: (NSString *) UserName {
if (_uesrname! = userName) {
[UserName retain];
[_username release];
_username = UserName;
}
}
Not only assignment operations, but also retain,release calls. If the property is Nonatomic, the setter method above is not an atomic operation, we can assume a scenario where thread 1 obtains the current _username through a getter, and then thread 2 calls [_username release] through the setter. , the _username that thread 1 holds becomes an invalid address space, and if you send a message to this address space, it will lead to crash, and the multi-threaded insecure scenario.

In the Arc era, Xcode has handled the retain and release for us, most of the time we do not need to care about memory management, but retain,release in fact still exist in the last run of code, Atomic and nonatomic the property declaration of the object class is theoretically different, but I actually use it, the nsstring* is set to nonatomic have never encountered the above-mentioned multithreading unsafe scenarios, It is very likely that the arc in memory management optimization has already processed the above scenario, so I personally feel that if only the object class property do Read,write,atomic and nonatomic in multithreading security and there is no actual difference.

The memory area to which the pointer property is pointing

This type of multi-threaded access scenario is a place where we are prone to error, even if we declare the property to be atomic. Because we're not accessing the property's pointer area, it's the memory area that the property points to. You can see the following code:

@property (Atomic, strong) nsstring* Stringa;

Thread A
for (int i = 0; i < 100000; i + +) {
if (i% 2 = = 0) {
Self.stringa = @ "A very long string";
}
else {
Self.stringa = @ "string";
}
NSLog (@ "Thread A:%@\n", Self.stringa);
}

Thread B
for (int i = 0; i < 100000; i + +) {
if (self.stringA.length >= 10) {
nsstring* subStr = [Self.stringa substringwithrange:nsmakerange (0, 10)];
}
NSLog (@ "Thread B:%@\n", Self.stringa);
}
Although Stringa is the property of atomic, and in taking substring time to make a length judgment, thread B is still very easy to crash, because in the first moment read length when Self.stringa = @ "A very long String ", the next moment the substring is taken, thread A has Self.stringa = @" string ", immediately appears out of the bounds of Exception,crash, multithreading is unsafe.

The same scenario also exists when working with collection classes, such as:

@property (Atomic, strong) nsarray* arr;

Thread A
for (int i = 0; i < 100000; i + +) {
if (i% 2 = = 0) {
Self.arr = @[@ "1", @ "2", @ "3"];
}
else {
Self.arr = @[@ "1"];
}
NSLog (@ "Thread A:%@\n", Self.arr);
}

Thread B
for (int i = 0; i < 100000; i + +) {
if (Self.arr.count >= 2) {
nsstring* str = [Self.arr objectatindex:1];
}
NSLog (@ "Thread B:%@\n", Self.arr);
}
Similarly, even though we did count before accessing Objectatindex, thread B is still easy to crash, because the memory area pointed to by arr between the two lines of code is modified by other threads.

So you see, the real need to worry about is this type of memory area access, even if declared as atomic is not used, our usual app appears inexplicably difficult to reproduce the multi-threaded crash many belong to this category, once in the multi-threaded scene to access this kind of memory area, to mention the more than of caution. How to avoid this kind of crash will be talked about later.

Property Multithreading Security Summary:

In short, atomic's role is only to give getter and setter a lock, atomic can only guarantee code into getter or setter function inside is safe, once out getter and setter, multithreading security can only rely on the programmer's own protection. So there is no direct connection between the atomic attribute and multithreading security using the property. In addition, atomic because of the lock will also bring some performance loss, so we write iOS code, the general declaration of the property for Nonatomic, in the need to do multi-threaded security scenes, their own to go extra lock to do synchronization.

How to do multithreading security?

Discussion here, in fact, how to do multithreading security is also more clear, the keyword is atomicity (atomic), as long as the atomic, small to a primitive type variable access, large to a long period of code logic execution, atomic performance Assurance code serial execution, There is no other thread involved to ensure that the code executes halfway.

Atomicity is a relative concept, and the object it targets, the granularity can be very small.

For example, the following code:

if (self.stringA.length >= 10) {
nsstring* subStr = [Self.stringa substringwithrange:nsmakerange (0, 10)];
}
is non-atomic in nature.

But after locking:

Thread A
[_lock Lock];
for (int i = 0; i < 100000; i + +) {
if (i% 2 = = 0) {
Self.stringa = @ "A very long string";
}
else {
Self.stringa = @ "string";
}
NSLog (@ "Thread A:%@\n", Self.stringa);
}
[_lock unlock];

Thread B
[_lock Lock];
if (self.stringA.length >= 10) {
nsstring* subStr = [Self.stringa substringwithrange:nsmakerange (0, 10)];
}
[_lock unlock];
The entire code is atomic and can be considered multithreaded security.

Another example:

if (Self.arr.count >= 2) {
nsstring* str = [Self.arr objectatindex:1];
}
is non-atomic in nature.

and

Thread A
[_lock Lock];
for (int i = 0; i < 100000; i + +) {
if (i% 2 = = 0) {
Self.arr = @[@ "1", @ "2", @ "3"];
}
else {
Self.arr = @[@ "1"];
}
NSLog (@ "Thread A:%@\n", Self.arr);
}
[_lock unlock];

Thread B
[_lock Lock];
if (Self.arr.count >= 2) {
nsstring* str = [Self.arr objectatindex:1];
}
[_lock unlock];
is atomic in nature. Note that both read and write require a lock.

This is why we do multithreading security, not by adding atomic keywords to the property to ensure security, but to declare the property as Nonatomic (Nonatomic no getter,setter lock overhead), and then lock themselves.

What kind of lock do I use?

There are many ways to lock code in iOS, which are commonly used:

@synchronized (token)
Nslock
dispatch_semaphore_t
Osspinlock
These types of locks can lead to atomicity, and the loss of performance is smaller from top to bottom.

My personal advice is, in writing the application layer code, in addition to the Osspinlock, which one handy. The correctness of the code logic is more important than the performance differences of the several locks. And the performance difference between these few people, most of the time are not aware of.

Of course, we also encounter a few scenarios that require code performance, such as writing the framework, or in a multi-threaded read-and-write shared data scenario, we need to know roughly how much of the loss the lock brings.

Official documentation has a data, using intel-based IMac with a 2 GHz Core Duo processor and 1 GB of RAM running OS X v10.5 test, get the mutex has about 0.2ms loss, we can think The loss of the lock is roughly at the MS level.

Atomic Operations

In fact, in addition to a variety of locks, iOS there is another way to obtain atomicity, Nanjing Lou Feng use atomic Operations, compared to the loss of the lock to a number of orders of magnitude or so, The use of these atomic operations can be seen in some third-party framework code that pursues high performance. These atomic operation can be found in the/usr/include/libkern/osatomic.h:

Like what

_inta + +;
is non-atomic in nature.

and

OSAtomicIncrement32 (& (_inta));
Is atomic, multithreaded secure.

Atomic operation can only be applied to 32-bit or 64-bit data types, and locks are used for scenarios where nsstring or Nsarray objects are used in multithreading.

Most of the atomic operation have osatomicxxx,osatomicxxxbarrier two versions, barrier is the memory barrier mentioned earlier, When there is a dependency between multiple multithreaded variables, the barrier version is used to guarantee the correct dependency order.

For the usual writing application layer multithreading security code, I still recommend that you use @synchronized,nslock, or dispatch_semaphore_t, multithreading security is more important than multithreading performance, should be fully guaranteed in the former, When there is spare time to pursue the latter.

Try to avoid multi-threaded design

No matter how much code we have written, Nanjing Lou Feng www.jsgren.com have to admit that multithreading security is a complex problem, as programmers we should avoid multithreaded design as much as possible, rather than pursue the skillful use of locks.

Later I will write an article about functional programming and its core ideas, even if we use non-functional programming language, such as objective C, can greatly help us to avoid the problem of multithreading security.

Summarize

The analysis of multithreading insecurity under iOS is over, how to write multithreaded security code, after all, is still in the memory layout and atomic understanding, but also hope this article will atomic and nonatomic the real difference explained clearly:).

Where is the iOS multithreading in the end unsafe?

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.