iOS project in a mix of Obj-C and C proper. I have a POD struct about 1 MB in size. There is a global instance of it. If I create a local instance of the same in a function that is invoked on a worker thread, the copy operation crashes (when invoked from a worker thread) in debug builds on a simulator. The release builds don't crash.
This smells like running out of stack size.
The worker thread in question is not created manually - is an NSOperationQueue
worker.
The questions are twofold:
- why does automatic stack growth fail?
- how does one increase stack size on
NSOperationQueue
threads?
The repro goes:
struct S
{
char s[1024*1024];
};
S gs;
-(void)f
{
S ls;
ls = gs; //Crash!
}
CodePudding user response:
I don't know how up to date this documentation is, but according to Apple non-main threads top out at 512kiB stack size unless otherwise configured during thread creation.
I struggle to see a good reason to store such a data structure on the stack, particularly with (Obj-)C where you can easily wrap it in something like a std::unique_ptr
which manages heap allocations and deallocations automatically. (Or indeed any other RAII based abstraction, or even storing it as an ivar in an ARC-enabled Objective-C class if you're so inclined.)
CodePudding user response:
Under the hood the threading mechanism of Cocoa uses unix POSIX threads for which stack size follows the following rules:
- Default stack size, if it's not explicitly specified (e.g. in macOS you can find this value by running
ulimit -s
command, which for my machine is8192
bytes, but for iOS is very likely a few times less) - Arbitrary stack size if it's specified during creating of a thread
So for the main thread of an application it's always the default system stack size, and can only be altered in macOS (or rooted iOS device) with use of ulimit
(size is given in KiB):
# Sets 32 MiB default stack size to a thread
% ulimit -s 32768
All other threads (to my knowledge) under both iOS and macOS have their size specified explicitly and it equals to 512 KiB. You will have to somehow forward the stack size to the pthread_create(3)
function for them, something like this:
#import <Foundation/Foundation.h>
#import <pthread.h>
struct S {
char s[1024 * 1024];
};
void *func(void *context) {
// 16 MiB stack variable
S s[16];
NSLog(@"Working thread is finished");
auto* result = new int{};
return result;
}
int main(int argc, const char * argv[]) {
pthread_attr_t attrs;
auto s = pthread_attr_init(&attrs);
// Allocates 32 MiB stack size
s = pthread_attr_setstacksize(&attrs, 1024 * 1024 * 32);
pthread_t thread;
s = pthread_create(&thread, &attrs, &func, nullptr);
s = pthread_attr_destroy(&attrs);
void* result;
s = pthread_join(thread, &result);
if (s) {
NSLog(@"Error code: %d", s);
} else {
NSLog(@"Main is finished with result: %d", *(int *)result);
delete (int *)result;
}
@autoreleasepool {
}
return 0;
}
Unfortunately neither of queue API (GCD
or NSOperation
) exposes allocation part of their thread pools, let alone that NSThread
doesn't let you to specify your own pthread
explicitly for underlying execution. If you want to rely on those APIs, you will have to implement it "artificially"