JHHK

欢迎来到我的个人网站
行者常至 为者常成

参考:线程管理

参考书籍 《多线程编程指南》
原著:Apple Inc.
翻译:謝業蘭 【老狼】

目录

每个应用程序启动时候都是一个线程,它执行程序的 main 函数。 应用程序可以生成额外的线程,其中每个线程执行一个特定功能的代码。
每个线程都拥有它自己的执行堆栈,由内核调度独立的运行时间片。
一个线程可以和其他线程或其他进程通信,执行 I/O 操作,甚至执行任何你想要它完成的任务。 因为它们处于相同的进程空间,所以一个独立应用程序里面的所有线程共享相同的虚拟内存空间,并且具有和进程相同的访问权限。

线程成本

一、性能成本
每个线程都需要分配一定的内核内存和应用程序内存空间的内存。这些数据结构里面的大部分都是当你首次创建线程或者进程的时候被创建和初始化的,它们所需的代价成本很高,因为需要和内核交互。
1.管理你的线程和协调其调度所需的核心数据结构存储在使用 Wired Memory 的内核里面。
2.你线程的堆栈空间和每个线程的数据都被存储在你应用程序的内存空间里面。

注意:因为底层内核的支持,操作对象(Operation objectis)可能创建线程更快。它们使用内核里面常驻线程池里面的线程来节省创建的时间,而不是每次都创建新的线程。

二、生产成本
当编写线程代码时另外一个需要考虑的成本是生产成本。设计一个线程应用程序有时会需要根本性改变你应用程序数据结构的组织方式。

创建一个线程

在所有情况下,你必须有一个函数或方法作为线程的主入口点,你必须使用一个可用的线程来启动你的线程。

一、使用NSThread
方式一:创建一个脱离线程

[NSThread detachNewThreadSelector:@selector(myThreadMainMethod:) toTarget:self withObject:@"test"];

-(void)myThreadMainMethod:(id)obj{
    //obj=test
    NSLog(@"obj=%@",obj);
    
    //thread = <NSThread: 0x126d8ef70>{number = 3, name = (null)}
    NSLog(@"thread = %@",[NSThread currentThread]);
}

方式二:创建一个脱离线程

NSThread * myThread = [[NSThread alloc] initWithTarget:self selector:@selector(myThreadMainMethod:) object:@"test"];

[myThread start];

-(void)myThreadMainMethod:(id)obj{
    //obj=test
    NSLog(@"obj=%@",obj);
    
    //thread = <NSThread: 0x126d8ef70>{number = 3, name = (null)}
    NSLog(@"thread = %@",[NSThread currentThread]);
}

二、使用POSIX的多线程
该方式创建的线程可跨平台,仅作了解。

三、使用NSObject来生成一个线程

ViewController * myObject = [[ViewController alloc] init];

[myObject performSelectorInBackground:@selector(myThreadMainMethod:) withObject:@"test"];

-(void)myThreadMainMethod:(id)obj{
    //obj=test
    NSLog(@"obj=%@",obj);
    
    //thread = <NSThread: 0x126d8ef70>{number = 3, name = (null)}
    NSLog(@"thread = %@",[NSThread currentThread]);
}

配置线程属性

创建线程之后或之前,你可能需要配置不同的线程环境。

一、配置线程的堆栈大小
对于每个你新创建的线程,系统会在你的进程空间里面分配一定的内存作为该线 程的堆栈。该堆栈管理堆栈帧,也是任何线程局部变量声明的地方。给线程分配的内 存大小在“线程成本”里面已经列举了。

NSThread * myThread = [[NSThread alloc] initWithTarget:self selector:@selector(myThreadMainMethod:) object:@"test"];
    
NSLog(@"stackSize = %zd",myThread.stackSize);//stackSize = 524288d

//This value must be in bytes and a multiple of 4KB.
//To change the stack size, you must set this property before starting your thread. 
[myThread setStackSize:2097152];

NSLog(@"stackSize = %zd",myThread.stackSize);//stackSize = 2097152d

[myThread start];

二、配置线程本地存储
每个线程都维护了一个键-值的字典,它可以在线程里面的任何地方被访问。

NSThread * myThread = [[NSThread alloc] initWithTarget:self selector:@selector(myThreadMainMethod:) object:@"test"];
    
[myThread.threadDictionary setObject:@"mythread" forKey:@"name"];

[myThread start];

-(void)myThreadMainMethod:(id)obj{
  /**
  dic={
    name = mythread;
  }
  */
  NSLog(@"dic=%@",[NSThread currentThread].threadDictionary);
}

三、设置线程的脱离状态
脱离线程(Detached thread)允许系统在线程完成的时候立即释放它的数据结构。 可连接线程(Joinable thread)资源被系统回收之前必须被其他线程连接。可连接线程同时提供了一个显示的方式来把数据从一个正在退出的线程传递到其他线程。

重要:在应用程序退出时,脱离线程可以立即被中断,而可连接线程则不可以。每个可连接 线程必须在进程被允许可以退出的时候被连接。所以当线程处于周期性工作而不允许被中断的时 候,比如保存数据到硬盘,可连接线程是最佳选择。

如果你想要创建可连接线程,唯一的办法是使用 POSIX 线程。

四、设置线程的优先级
你创建的任何线程默认的优先级是和你本身线程相同。内核调度算法在决定该运 行那个线程时,把线程的优先级作为考量因素,较高优先级的线程会比较低优先级的 线程具有更多的运行机会。较高优先级不保证你的线程具体执行的时间,只是相比较 低优先级的线程,它更有可能被调度器选择执行而已。

重要:让你的线程处于默认优先级值是一个不错的选择。增加某些线程的优先级,同时有可 能增加了某些较低优先级线程的饥饿程度。如果你的应用程序包含较高优先级和较低优先级线 程,而且它们之间必须交互,那么较低优先级的饥饿状态有可能阻塞其他线程,并造成性能瓶颈。

如果你想改变线程的优先级,Cocoa 和 POSIX 都提供了一种方法来实现。对于 Cocoa 线程而言,你可以使用 NSThread 的 setThreadPriority:类方法来设置当前运 行线程的优先级。对于 POSIX 线程,你可以使用 pthread_setschedparam 函数来实现。 关于更多信息,参与 NSThread Class Reference 或 pthread_setschedparam 主页。

NSThread * myThread = [[NSThread alloc] initWithTarget:self selector:@selector(myThreadMainMethod:) object:@"test"];

NSLog(@"threadPriority=%lf",myThread.threadPriority);//threadPriority=0.500000

[myThread setThreadPriority:1.0];

NSLog(@"threadPriority=%lf",myThread.threadPriority);//threadPriority=1.000000

[myThread start];

编写你线程的主体入口点

Mac OS X 上面线程结构的主体入口点和其他平台基本一样。你 需要初始化你的数据结构,做一些工作或可行的设置一个 run loop,并在线程代码被执行完后清理它。根据设计,当你写的主体入口点的时候有可能需要采取一些额外 的步骤。

一、创建一个自动释放池(Autorelease Pool)
如果你的应用程序使用内存管理模型,在你编写线程主体入口的时候第一件事情就是创建一个自动释放池。同样,在你的线程最后应该销毁该自动释放池。该池保证自动释放。 长时运行的线程需要新建额外的自动释放池来更频繁的释放它的对象。比如,一个使用 run loop 的线程可能在每次运行完一次循环的时候创建并释放该自动释放池。更频繁的释放对象可以防止你的应用程序内存占用太大造成性能问题。虽然对于任何与性能相关的行为,你应该测量你代码的实际表现,并适当地调整使用自动释放池。

-(void)myThreadMainRoutine{

  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Top-level pool
    
  // Do thread work here.

  [pool release]; // Release the objects in the pool.
}
int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

二、设置异常处理
在你线程的主体入口点安装一个 try/catch 模块,可以让你捕获任何未知的异常,并提供一个合适 的响应。

三、设置一个 Run Loop
当你想编写一个独立运行的线程时,你有两种选择。
第一种选择是写代码作为一个长期的任务,很少甚至不中断,线程完成的时候退出。这种方法不需要在你的代码指定任何东西;你只需要启动的时候做你打算做的事情即可。
第二种选择是把你的线程放入一个循环里面,让它动态的处理到来的任务请求。这种方法需要在你的线程里面添加一个 run loop。

中断线程

退出一个线程推荐的方法是让它在它主体入口点正常退出。尽管 Cocoa、POSIX 和 Multiprocessing Services 提供了直接杀死线程的例程,但是使用这些例程是强烈不鼓励的。杀死一个线程阻止了线程本身的清理工作。线程分配的内存可能造成泄露,并且其他线程当前使用的资源可能没有被正确清理干净,之后造成潜在的问题。

如果你的应用程序需要在一个操作中间中断一个线程,应该设计线程的响应 取消或退出消息。对于长时运行的操作,这意味着要周期性的停止工作来检查这些消息是否到来。如果该消息到来并要求线程退出,那么线程就有机会来执行清理和退出工作;否则它会继续工作和处理下一个数据块。

响应取消消息的一个方法是使用 run loop 的输入源来接收这些消息。

- (void)threadMainRoutine{
   
  BOOL moreWorkToDo = YES;
    
  BOOL exitNow = NO;
    
  NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
    

  // Add the exitNow BOOL to the thread dictionary.
  NSMutableDictionary* threadDict = [[NSThread currentThread] threadDictionary];
  [threadDict setValue:[NSNumber numberWithBool:exitNow] forKey:@"ThreadShouldExitNow"];
    
    
  // Install an input source.
  [self myInstallCustomInputSource];
    
    
  while (moreWorkToDo && !exitNow){
    
    // Do one chunk of a larger body of work here.
      
    // Change the value of the moreWorkToDo Boolean when done.
      
      
    // Run the run loop but timeout immediately if the input source isn't waiting to fire.
    [runLoop runUntilDate:[NSDate date]];
      
      
    // Check to see if an input source handler changed the exitNow value.
    exitNow = [[threadDict valueForKey:@"ThreadShouldExitNow"] boolValue];
  }
   
}

行者常至,为者常成!





R
Valine - A simple comment system based on Leancloud.