ref: http://stackoverflow.com/questions/79923/what-and-where-are-the-stack-and-heap
Local variables are stored in each thread’s own stack. That means that local variables are never shared between threads.
The OS allocates the stack for each system-level thread when the thread is created.
That also means that all local primitive variables are thread safe.
For example, in objective C:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
-(void) bigTask:(NSString *)identifier { int localVar = 6680; NSLog(@"Thread %@ is looking at local variable with address %p", identifier, &localVar); } .... .... [NSThread detachNewThreadSelector:@selector(bigTask:) toTarget:self withObject:@"A"]; [NSThread detachNewThreadSelector:@selector(bigTask:) toTarget:self withObject:@"B"]; NSLog(@"done"); |
The result is:
Thread B is looking at local variable with address 0x102b87c30
Thread A is looking at local variable with address 0x102affc30
Each thread gets a stack, while there’s typically only one heap for the application.
Typically the OS is called by the language runtime to allocate the heap for the application.
The stack is attached to a thread, so when the thread exits the stack is reclaimed. The heap is typically allocated at application startup by the runtime, and is reclaimed when the application (technically process) exits.
The size of the stack is set when a thread is created. The size of the heap is set on application startup, but can grow as space is needed (the allocator requests more memory from the operating system).
The stack is faster because the access pattern makes it trivial to allocate and deallocate memory from it (a pointer/integer is simply incremented or decremented), while the heap has much more complex bookkeeping involved in an allocation or free. Also, each byte in the stack tends to be reused very frequently which means it tends to be mapped to the processor’s cache, making it very fast. Another performance hit for the heap is that the heap, being mostly a global resource, typically has to be multi-threading safe, i.e. each allocation and deallocation needs to be – typically – synchronized with “all” other heap accesses in the program.
In a multi-threaded environment each thread will have its own completely independent stack but they will share the heap. Concurrent access has to be controlled on the heap and is not possible on the stack.
Example, let’s have a member variable created on the heap like so:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
@interface AppDelegate () @property(nonatomic, retain) NSNumber * objectInHeap; @end .. .. .. @implementation AppDelegate -(void) bigTask:(NSString *)identifier { int localVar = 6680; NSLog(@"Thread %@ is looking at Logic object in heap with address %p", identifier, self.objectInHeap); NSLog(@"Thread %@ is looking at local variable with address %p", identifier, &localVar); } - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.objectInHeap = [[NSNumber alloc] initWithInt:0]; [NSThread detachNewThreadSelector:@selector(bigTask:) toTarget:self withObject:@"A"]; [NSThread detachNewThreadSelector:@selector(bigTask:) toTarget:self withObject:@"B"]; } @end |
result is:
Thread A is looking at Logic object in heap with address 0xb000000000000002
Thread B is looking at Logic object in heap with address 0xb000000000000002
Thread A is looking at local variable with address 0x102ab3c44
Thread B is looking at local variable with address 0x102b3bc44
So as you can see, the local variable is unique to each thread. However, the object created on the heap, given that it was made previously and created once, is seen by BOTH threads.
Number object example (part 1) – sharing of same heap objects
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
-(void)doIt:(NSString*)identifier { NSLog(@"---- BEGIN DO IT: address of identifier %@ is: %p ----", identifier, identifier); self.objectInHeap = [NSNumber numberWithInt:0]; for (int i = 0; i < 10; i++) { self.objectInHeap = [NSNumber numberWithInt:i]; NSLog(@"Thread %@ changed number in heap to: %u, object in heap address %p", identifier, i, self.objectInHeap); NSLog(@"Thread %@ 's number object address %p", identifier, &_objectInHeap); } NSLog(@"---- END DO IT: after execution: %i for thread %@ ----", [self.objectInHeap intValue], identifier); } |
For example, in the beginning, thread A creates NSNumber 0 with address 0xb000000000000002.
Then thread C comes in and creates NSNumber 0 but it points to the same object with the address 0xb000000000000002
Because the heap is a global resource, it has its own data structure that it looks through to see if there already is an object with type NSNumber and value 0 created. If it does, it’ll just return it for use.
The heap’s main objective is to be efficient and save space and execution time, thus, if it sees that multiple threads wishes to access the same object type and value, it will return that common object from its data structure and thus just retain it. When all of the threads finish and point away from it, it will see that the commonly used object retain count is 0 and thus releases it from its heap data structure.
Thus, you will see that as Thread ‘A’, ‘C’, and ‘B’ come in to create NSNumber with value 0, the heap returns the same object of address 0xb000000000000002.
Difference from part 2
The reason why in part 2, every newly allocated object has different address is because I used random to generate a different starting point number. Thus for each thread, there are no same numbers. Each thread works off of a different starting number.
Thread ‘A’ started from 16807, while thread ‘B’ started off 282475249. Thus NSNumber objects with different values will obviously need to be allocated with their own address space.
—- BEGIN DO IT: address of identifier A is: 0x1009c01e8 —-
—- BEGIN DO IT: address of identifier C is: 0x1009c0228 —-
Thread A changed number in heap to: 0, object in heap address 0xb000000000000002
Thread C changed number in heap to: 0, object in heap address 0xb000000000000002
—- BEGIN DO IT: address of identifier B is: 0x1009c0208 —-
Thread A ‘s number object address 0x7f9771501cc0
Thread C ‘s number object address 0x7f9771501cc0
Thread B changed number in heap to: 0, object in heap address 0xb000000000000002
Thread A changed number in heap to: 1, object in heap address 0xb000000000000012
Thread B ‘s number object address 0x7f9771501cc0
Thread A ‘s number object address 0x7f9771501cc0
Thread C changed number in heap to: 1, object in heap address 0xb000000000000012
Thread B changed number in heap to: 1, object in heap address 0xb000000000000012
—- BEGIN DO IT: address of identifier D is: 0x1009c0248 —-
Thread A changed number in heap to: 2, object in heap address 0xb000000000000022
Thread C ‘s number object address 0x7f9771501cc0
Thread B ‘s number object address 0x7f9771501cc0
Thread D changed number in heap to: 0, object in heap address 0xb000000000000002
Thread A ‘s number object address 0x7f9771501cc0
Thread C changed number in heap to: 2, object in heap address 0xb000000000000022
Thread B changed number in heap to: 2, object in heap address 0xb000000000000022
Thread D ‘s number object address 0x7f9771501cc0
Thread A changed number in heap to: 3, object in heap address 0xb000000000000032
Number object example (part 2) – Another example
Say we have an instance object’s method like so:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
-(void)doIt:(NSString*)identifier { NSLog(@"---- BEGIN DO IT: address of identifier %@ is: %p ----", identifier, identifier); int startNumber = rand(); NSLog(@"start number %u assigned to thread %@, should end with: %u", startNumber, identifier, startNumber+100); self.objectInHeap = [NSNumber numberWithInt:startNumber]; for (int i = startNumber; i < startNumber+10; i++) { self.objectInHeap = [NSNumber numberWithInt:i]; NSLog(@"Thread %@ changed number in heap to: %u, object in heap address %p", identifier, i, self.objectInHeap); NSLog(@"Thread %@ 's number object address %p", identifier, &_objectInHeap); } NSLog(@"---- END DO IT: after execution: %i for thread %@ ----", [self.objectInHeap intValue], identifier); } |
We spawn multiple threads to run over that method:
1 2 3 4 5 6 7 |
[NSThread detachNewThreadSelector:@selector(doIt:) toTarget:self withObject:@"A"]; [NSThread detachNewThreadSelector:@selector(doIt:) toTarget:self withObject:@"B"]; |
Here is what’s happening:
1) First thread comes into the method, it pushes the identifier onto its stack. Remember all parameters and local variables gets pushed onto the thread’s own stack. You can verify this by looking at the address of the parameter ‘identifier’. Both threads have different ‘identifier’ variables.
Thread ‘A’ uses the random function to get a start value of 16807
It then manipulates our class member variable, _objectInHeap, which scope is global to all those who use this class. You can look at the _objectInHeap’s address at each thread’s manipulation to see that they are all manipulating the same _objectInHeap.
When thread ‘A’ points _objectInHeap to a new object in heap, that NSNumber object is allocated and auto-released.
2) You will then see the second thread with name ‘B’ come in. We see that the identifier pushed onto this thread’s is different than thread ‘A’.
Thread ‘B’ uses the random function to get a start value of 282475249.
3) Next step, our thread ‘A’ then re-points _objectInHeap pointer to another object in the heap. Notice that objectInHeap is the same, and the object in the heap has a different address. That’s because the heap allocated a new object with a new addresses so that _objectInHeap can point to it. The previous NSNumber object, assuming now that no one is pointing to it, would be auto-released by the heap data structure.
4) Thread ‘B’ now manipulates _objectInHeap to point to a newly allocated NSNumber with value 282475249.
Hence both threads ‘A’ and ‘B’ uses the same class member variable pointer _objectInHeap and basically both are pulling _objectInHeap’s pointer to their own commands of allocated objects in the heap.
result:
2015-05-20 09:09:11.721 NSNotificationExample[3621:483901] —- BEGIN DO IT: address of identifier A is: 0x10346d210 —-
2015-05-20 09:09:11.722 NSNotificationExample[3621:483901] start number 16807 assigned to thread A, should end with: 16907
2015-05-20 09:09:11.722 NSNotificationExample[3621:483901] Thread A changed number in heap to: 16807, object in heap address 0xb000000000041a72
2015-05-20 09:09:11.722 NSNotificationExample[3621:483901] Thread A ‘s number object address 0x7fd7e0730630
2015-05-20 09:09:11.722 NSNotificationExample[3621:483902] —- BEGIN DO IT: address of identifier B is: 0x10346d230 —-
2015-05-20 09:09:11.723 NSNotificationExample[3621:483901] Thread A changed number in heap to: 16808, object in heap address 0xb000000000041a82
2015-05-20 09:09:11.723 NSNotificationExample[3621:483902] start number 282475249 assigned to thread B, should end with: 282475349
2015-05-20 09:09:11.723 NSNotificationExample[3621:483901] Thread A ‘s number object address 0x7fd7e0730630
2015-05-20 09:09:11.723 NSNotificationExample[3621:483902] Thread B changed number in heap to: 282475249, object in heap address 0xb00000010d63af12
2015-05-20 09:09:11.723 NSNotificationExample[3621:483901] Thread A changed number in heap to: 16809, object in heap address 0xb000000000041a92