http://shaheengandhi.com/controlling-thread-exit/

While most concurrency can be dealt with by using GCD dispatch queues, there are some things for which you will want to use a separate NSThread. For instance, concurrent network programming remains mostly in this area. You may consider using the popular CocoaAsyncSocket library, which abstracts away much of the complexity of socket programming on iOS. But, for the sake of this post, assume some work requires a separate thread, and we desire to cleanly start and stop the thread. That is, when we decide to start or stop the thread, we want to ensure it has been initialized or destroyed before moving on.

You can get the final version of the code example below at github.

Thread Setup

Remember that threads are an artefact of the operating system and not the objective-c runtime. That means all the nice stuff like an NSAutoreleasePooland an NSRunLoop need to be managed by the thread's top level code. Here is a code snippet of a thread's main method that sets up an autorelease pool as well as starts pumping NSRunLoop.

  1. - (void)start
  2. {
  3. if (_thread) {
  4. return;
  5. }
  6. _thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadProc:) object:nil];
  7. }
  8. - (void)threadProc:(id)ignored
  9. {
  10. @autoreleasepool {
  11. // Startup code here
  12. // Just spin in a tight loop running the runloop.
  13. do {
  14. [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]]
  15. } while (TRUE);
  16. }
  17. }

This code has many problems. Note that each run of the runloop has a timeout of 1 second. When the runloop has nothing to do for extended periods of time, the thread will still wake up every second and go through the while loop's code, only to end up sitting in the runloop's wait condition. That burns CPU and battery. Also, this thread has no exit condition. Even if we want the thread to live until the end of the application's life, we probably want a way to shut it down cleanly. Here's an enumeration of the problems we want to fix:

  1. Unsure when the thread is ready for work
  2. Never goes to sleep
  3. No way to exit the thread cleanly

Solving all three problems at once is tricky. Fixing #2 means keeping the runloop asleep, preferrably waking only when there is work to be done (being notified by a runloop source). But, that makes it difficult to exit the thread in a timely manner.

Wait for Infinity

How can we make NSRunLoop wait for infinity? The goal is to make sure that the thread goes to sleep. Looking at the documentation for runUntilDate:, this is not the case:

If no input sources or timers are attached to the run loop, this method exits immediately; otherwise, it runs the receiver in the NSDefaultRunLoopMode by repeatedly invoking runMode:beforeDate: until the specified expiration date.

The second sentence means this code keeps spinning on the CPU, which is the exact opposite of our desired behavior. A better alternative isrunMode:beforeDate:, for which the documentation reads:

If no input sources or timers are attached to the run loop, this method exits immediately and returns NO; otherwise, it returns after either the first input source is processed or limitDate is reached.

At least there's a chance the thread will sleep when using this method. However, the thread still runs in a continuous loop when there are no sources or timers. If you are only using a run loop to dispatch performSelector: calls, you need to add a runloop source of your own to make the thread sleep. As a side effect, it would be effective to use this source to send work to the thread, but that is an exercise left to the reader.

One last thing: what value of NSDate should be used? Any large value is fine, as waking the thread once a day is a large enough to keep the thread silent.+[NSDate distantFuture] is a convenient factory for such a date.

  1. static void DoNothingRunLoopCallback(void *info)
  2. {
  3. }
  4. - (void)threadProc:(id)ignored
  5. {
  6. @autoreleasepool {
  7. CFRunLoopSourceContext context = {0};
  8. context.perform = DoNothingRunLoopCallback;
  9. CFRunLoopSourceRef source = CFRunLoopSourceCreate(NULL, 0, &context);
  10. CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
  11. do {
  12. [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
  13. beforeDate:[NSDate distantFuture]];
  14. } while (TRUE);
  15. CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
  16. CFRelease(source);
  17. }
  18. }

The exit condition

Now that the thread sleeps forever, how can we ensure a clean shutdown of the thread? It's entirely possible to use +[NSThread exit] to kill the currently running thread. But, this does not clean up stack references to heap objects (like the runloop source that is created), drain the top level autorelease pool, nor allows the thread to flush any remaining work. Instead, we need to make the runloop stop sleeping in order to exit runMode:beforeDate:, and we need a condition the thread can check in order to know it's time to shut down.

NSRunLoop's conditions for exiting runMode:beforeDate: are somewhat limited. Its return values being YES (when the runloop processed an input source or if the timeout was reached) or NO (when the runloop could not be started) aren't great. Given that the documentation states that NO is returned only when there are no sources or timers, our code will never see NO returned since we added an input source to keep the runloop from spinning!

Luckily, NSRunLoop controls the same object that CFRunLoop APIs do. The CoreFoundation alternative is to use CFRunLoopRunInMode, which provides a more specific reason for the runloop's exit. Specifically,kCFRunLoopRunStopped indicates the runloop was stopped byCFRunLoopStop. This is also the reason that CFRunLoopRun exits (other than having no sources or times, but that will not occur due to our fake source), so we don't need to bother with CFRunLoopRunInMode, nor check a condition.

It is best to execute CFRunLoopStop on the target thread itself. We can do this by using performSelector:onThread:withObject:waitUntilDone:.

The new threadProc: looks like this:

  1. - (void)threadProc:(id)ignored
  2. {
  3. @autoreleasepool {
  4. CFRunLoopSourceContext context = {0};
  5. context.perform = DoNothingRunLoopCallback;
  6. CFRunLoopSourceRef source = CFRunLoopSourceCreate(NULL, 0, &context);
  7. CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
  8. // Keep processing events until the runloop is stopped.
  9. CFRunLoopRun();
  10. CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
  11. CFRelease(source);
  12. }
  13. }

We can use code like the following to exit the thread quickly from any thread, including the target thread itself:

  1. - (void)stop
  2. {
  3. [self performSelector:@selector(_stop) onThread:_thread withObject:nil waitUntilDone:NO];
  4. _thread = nil;
  5. }
  6. - (void)_stop
  7. {
  8. CFRunLoopStop(CFRunLoopGetCurrent());
  9. }

Synchronizing thread startup and exit

Two more problems to solve: when starting the thread, can we ensure that it is ready? When shutting down a thread, can we ensure it is gone?

I would note that there are better threading patterns than attempting to ensure the state of the target thread. For example, a thread should be able to accept work, but simply not process it until it is ready. Resources outside of the thread's runloop should be minimal such that ensuring the thread is no longer executing should be above and beyond the desired knowledge of the thread's state.

It is tempting to simply add waitUntilDone:YES to the performSelectorstatement in order to wait until the target thread has exited, but that would only wait until the _stop selector was invoked, and not wait for the thread's cleanup code to be run. In order to do that, we need to make a new assumption: the target thread is being shut down from another thread. It would be impossible for a thread to wait for itself to shut down.

In order for the target thread to signal to the control thread that it is done, a condition must be shared between them. NSCondition provides convenient semantics for our purposes.

The thread management code is below. This pattern keeps a thread with no work asleep for long periods of time, while allowing for a fast and clean exit. It also allows for startup and shutdown to be synchronized.

  1. - (void)start
  2. {
  3. if (_thread) {
  4. return;
  5. }
  6. _thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadProc:) object:nil];
  7. // _condition was created in -init
  8. [_condition lock];
  9. [_thread start];
  10. [_condition wait];
  11. [_condition unlock];
  12. }
  13. - (void)stop
  14. {
  15. if (!_thread) {
  16. return;
  17. }
  18. [_condition lock];
  19. [self performSelector:@selector(_stop) onThread:_thread withObject:nil waitUntilDone:NO];
  20. [_condition wait];
  21. [_condition unlock];
  22. _thread = nil;
  23. }
  24. - (void)threadProc:(id)object
  25. {
  26. @autoreleasepool {
  27. CFRunLoopSourceContext context = {0};
  28. context.perform = DoNothingRunLoopCallback;
  29. CFRunLoopSourceRef source = CFRunLoopSourceCreate(NULL, 0, &context);
  30. CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
  31. [_condition lock];
  32. [_condition signal];
  33. [_condition unlock];
  34. // Keep processing events until the runloop is stopped.
  35. CFRunLoopRun();
  36. CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
  37. CFRelease(source);
  38. [_condition lock];
  39. [_condition signal];
  40. [_condition unlock];
  41. }
  42. }

Ensuring Destruction of Thread Resources

There is still another issue with this code: when the thread has signaled its exit, the autorelease pool has not yet been drained. Without ensuring that the thread's memory resources have been released, the purpose of synchronizing the thread's exit becomes much less appealing.

There's a bit of a catch-22. NSCondition makes no promise that it is free from using -autorelease in its implementations of -lock-signal, and-unlock. That means there should be a valid NSAutoreleasePool when using these APIs. We have two solutions available to us. We can either manually drain the autorelease pool, or use a different way to synchronize the thread's exit that waits until threadProc: has exited. The first is somewhat messy. The second has two variants of its own.

Manual Autorelease

In order to use NSAutoreleasePool directly, you must disable ARC.

Remember that -[NSAutoreleasePool drain] is effectively the same as-[NSAutoreleasePool release] and that the pool is no longer valid after draining it. So, manually draining an autorelease pool means creating another one to ensure the NSCondition APIs have the right environment.

  1. - (void)threadProc:(id)object
  2. {
  3. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  4. {
  5. CFRunLoopSourceContext context = {0};
  6. context.perform = DoNothingRunLoopCallback;
  7. CFRunLoopSourceRef source = CFRunLoopSourceCreate(NULL, 0, &context);
  8. CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
  9. [_condition lock];
  10. [_condition signal];
  11. [_condition unlock];
  12. // Keep processing events until the runloop is stopped.
  13. CFRunLoopRun();
  14. CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
  15. CFRelease(source);
  16. // Release all accumulated resources, but make sure NSCondition has the
  17. // right environment.
  18. [pool drain];
  19. pool = [[NSAutoreleasePool alloc] init];
  20. [_condition lock];
  21. [_condition signal];
  22. [_condition unlock];
  23. }
  24. [pool drain];
  25. }

Using NSThreadWillExitNotification

NSThreadWillExitNotification is a notification sent by NSThread when the thread's main function has finished and the thread is about to finish execution. This must happen after threadProc:, so this ensures the thread's top level autorelease pool has been drained. Since the notification fires on the exiting thread, NSCondition is still used to synchronize the state of the thread.

  1. - (void)stop
  2. {
  3. if (!_thread) {
  4. return;
  5. }
  6. NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
  7. [nc addObserver:self selector:@(_signal) name:NSThreadWillExitNotification object:_thread];
  8. [_condition lock];
  9. [self performSelector:@selector(_stop) onThread:_thread withObject:nil waitUntilDone:NO];
  10. [_condition wait];
  11. [_condition unlock];
  12. [nc removeObserver:self name:NSThreadWillExitNotification object:_thread];
  13. _thread = nil;
  14. }
  15. - (void)threadProc:(id)object
  16. {
  17. @autoreleasepool {
  18. CFRunLoopSourceContext context = {0};
  19. context.perform = DoNothingRunLoopCallback;
  20. CFRunLoopSourceRef source = CFRunLoopSourceCreate(NULL, 0, &context);
  21. CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
  22. [_condition lock];
  23. [_condition signal];
  24. [_condition unlock];
  25. // Keep processing events until the runloop is stopped.
  26. CFRunLoopRun();
  27. CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
  28. CFRelease(source);
  29. }
  30. }
  31. - (void)_signal
  32. {
  33. [_condition lock];
  34. [_condition signal];
  35. [_condition unlock];
  36. }

Using pthreads

All of the above solutions have one slight problem: the thread hasn't quite exited when the control thread believes it has. The target thread is very close to being done, but that is not the same as done.

To ensure that, we must go outside the realm of NSThread and use pthreads instead. pthread_join guarantees the target thread has terminated. Using pthreads makes the code a bit more verbose, and some of the memory management must be done carefully. In particular, self is retained when specifying it as an argument of NSThread's initializer, but will not be retained when using it as an argument to pthread_create. Note that we still need anNSThread reference to useperformSelector:onThread:withObject:waitUntilDone:, but there is no way to convert a pthread_t to an NSThread. Luckily,+[NSThread currentThread] can obtain the correct object reference.

NSCondition is still used to synchronize thread startup. Because it is not used for any other purpose, it is not necessary to lock the condition before starting the thread. However, to be consistent with previous code, we will follow the previous pattern of creating the thread in a suspended state and resuming it with the condition's lock held.

  1. static void *ThreadProc(void *arg)
  2. {
  3. ThreadedComponent *component = (__bridge_transfer ThreadedComponent *)arg;
  4. [component threadProc:nil];
  5. return 0;
  6. }
  7. - (void)start
  8. {
  9. if (_thread) {
  10. return;
  11. }
  12. if (pthread_create_suspended_np(&_pthread, NULL, &ThreadProc, (__bridge_retained void *)self) != 0) {
  13. return;
  14. }
  15. // _condition was created in -init
  16. [_condition lock];
  17. mach_port_t mach_thread = pthread_mach_thread_np(_pthread);
  18. thread_resume(mach_thread);
  19. [_condition wait];
  20. [_condition unlock];
  21. }
  22. - (void)stop
  23. {
  24. if (!_thread) {
  25. return;
  26. }
  27. [self performSelector:@selector(_stop) onThread:_thread withObject:nil waitUntilDone:NO];
  28. pthread_join(_pthread, NULL);
  29. _thread = nil;
  30. }
  31. - (void)threadProc:(id)object
  32. {
  33. @autoreleasepool {
  34. CFRunLoopSourceContext context = {0};
  35. context.perform = DoNothingRunLoopCallback;
  36. CFRunLoopSourceRef source = CFRunLoopSourceCreate(NULL, 0, &context);
  37. CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
  38. // Obtain the current NSThread before signaling startup is complete.
  39. _thread = [NSThread currentThread];
  40. [_condition lock];
  41. [_condition signal];
  42. [_condition unlock];
  43. // Keep processing events until the runloop is stopped.
  44. CFRunLoopRun();
  45. CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
  46. CFRelease(source);
  47. }
  48. }

Controlling How NSThread and NSRunLoop Exit的更多相关文章

  1. NSThread 、NSRunLoop 和 Dispatch Queue

     iOS多线程编程中,NSOperation和NSOperationQueue无疑是最常用的,它们能满足绝大部分情况下的线程操作.但在完成一些特殊的任务时,我们还是要使用的NSThread和NSRun ...

  2. NSTimer、 NSTask、 NSThread 和 NSRunloop 之间的区别

    NSTimer是一个计时器对象,方法调用在对未来的选择对象. NSThread是一个线程类. 也就是创建一个线程. NSTask类是一个过程,一种方式运行程序从您的其他程序. NSOperation是 ...

  3. NSThread 子线程 Cocoa NSOperation GCD(Grand Central Dispatch) 多线程

    单词:thread 英 θred:n 线.思路.vt 穿过.vi 穿透过 一.    进程.线程 进程:正在进行中的程序被称为进程,负责程序运行的内存分配,每一个进程都有自己独立的虚拟内存空间 线程: ...

  4. iOS-----多线程之NSThread

    多线程 iOS平台提供了非常优秀的多线程支持,程序可以通过非常简单的方式来启动多线程,iOS平台不仅提供了NSThread类来创建多线程,还提供了GCD方式来简化多线程编程,提供了NSOperatio ...

  5. iOS 如何保持线程一直在运转(二)

    一.接着上一篇通过NSThread可以方便的创建一个线程,并且启动线程的Runloop,在线程体中执行一个while循环 然后我们就可以方便得利用这个线程了 - (void)threadRun:(NS ...

  6. 深入研究 Runloop 与线程保活

    深入研究 Runloop 与线程保活 在讨论 runloop 相关的文章,以及分析 AFNetworking(2.x) 源码的文章中,我们经常会看到关于利用 runloop 进行线程保活的分析,但如果 ...

  7. Runloop理解

    看了一堂公开课,自己小结一下: Runloop: 内部有三个东东:(Source, Timer, Observer) 作用/本质:1.死循环 (为app 保活): 2.监听处理事件 Timer 理解: ...

  8. 第2月第1天 GCDAsyncSocket dispatch_source_set_event_handler

    一.GCDAsyncSocket的核心就是dispatch_source_set_event_handler 1.accpet回调 accept4Source = dispatch_source_cr ...

  9. 【引】objective-c,6:Autorelease Pool

    参考博客: http://blog.leichunfeng.com/blog/2015/05/31/objective-c-autorelease-pool-implementation-princi ...

随机推荐

  1. Linux开发工具之Makefile(下)

    二.Makefile(下) 01.make常用内嵌函数 函数调用   $(function arguments) $(wildcard PATTERN)   当前目录下匹配模式的文件   例如:src ...

  2. HDU 2639 (01背包第k优解)

    /* 01背包第k优解问题 f[i][j][k] 前i个物品体积为j的第k优解 对于每次的ij状态 记下之前的两种状态 i-1 j-w[i] (选i) i-1 j (不选i) 分别k个 然后归并排序并 ...

  3. Sublime Text插件之Emmet

    转载:http://www.w3cplus.com/tools/using-emmet-speed-front-end-web-development.html Emmet插件以前被称作为Zen Co ...

  4. javascript 实用函数

    1.去除字符串空格 /*去左空格*/ function ltrim(s) { return s.replace(/^(\s*| *)/, ""); } /*去右空格*/ funct ...

  5. Android设备 cocos2dx 骨骼动画注册事件播放音效,退到后台再返回黑屏问题

    最近遇到一个cocos2dx 骨骼动画注册事件播放音效,在骨骼动画播放的时候,按HOME键退到桌面,再次打开游戏的时候,会黑屏. 解决办法如下,可能不是太完美,至少解决了大部分问题. 1.在org.c ...

  6. struts2与spring整合问题,访问struts2链接时,spring会负责创建Action

    每次访问一次链接,spring会创建一个对象,并将链接所带的参数注入到Action的变量中(如何做到的呐) 因为: struts2的action每次访问都重新创建一个对象,那spring的ioc是怎么 ...

  7. 从cellForRowAtIndexPath 看cell的重用机制

    今天突然发现一个问题,由于对UITableViewCell 的重用机制不是很了解,让我纠结很久: 用过reloadData时候,会调用cellForRowAtIndexPath方法,但是请看以下2种c ...

  8. Swift 提示 error running playground...

    创建playground之后,我们将得到一个错误提示,Error running playground: Failed to prepare for communication with playgr ...

  9. 不同浏览器对parseInt方法解析的不同

    parseInt方法的作用是将字符串转换为数字 当parseInt解析的时候只有0x和非0开头的数字,浏览器解析都一致,例如”0xA1”或 “9”. 只有当开头为0的时候才出现不同.IE,chrome ...

  10. Asp.net的IP地址屏蔽功能设计

    "IP地址的长度为32位,分为4段,每段8位,用十进制数字表示,每段数字范围为0~255,段与段之间用句点隔开." 由此我们了解到,IP地址实际上是一个32位正整数,在C#中可以使 ...