线程(二)Thread Management


最好的interface,就是冲document 开始。

talk is cheap, coding show is super, Document make comfortable;


原文参考:Thread ManageMent , go to ~





==============线程管理 (Thread Management)==========

    关于apple的应用程序和线程:

        1. OS系统(OS X iOS)的进程(应用程序)都是有一个或多个线程构成,每一个线程代表着一条应用程序代码的执行路径。每一个应用程序都起始于一个线程(主线程),由应用程序的 main 函数启动。应用程序可以添加生成新的线程,并且由指定的代码函数进行调用执行。

        2. 应用程序生成新线程之后,该线程就会成一个一存在于应用程序控件的独立实体。它将有自己的执行堆栈,并且被内核(kernel)在运行时被分离开来。线程间可以进行通信,线程也可以与别的进程进行通信,可以执行I/O 操作或者一些别的任务。不过由于是在同一个进程中,所有线程共享应用程序的同一块虚拟内存控件,并且对其有一样的访问权限。

        4. 本章内容就是一个OS 系统中线程应用技术的概览。


(一)线程开销(Thread Costs

    1. 线程执行对项目和系统,在内存和性能上都会有消耗,每一个线程都需要同时开辟项目内存和系统内核存储空间。核结构需要kernel使用总线内存(wired memory)去管理协调线程。而线程自己的堆栈和线程数据是存在项目内存空间的。这些数据结构大多数在创建线程的时候就被初始化,对于一个进程来说代价这样的代价是昂贵的(因为需要与内核(kernel)交互)。


    2. 下面大概统计一下创建线程时候的这些花销,这些数据自然是可以在具体进程中进行配置的,只是作为参考

        <1>.内核数据结构(Kernel data structures  | 大约 1KB  | 这些记忆体用于存储线程数据结构和属性,大多数是在总线记忆体开辟的,所以不能分页到磁盘空间。

        <2>.堆栈空间(Stack space| 异步线程:512 KB OS X 主线程:8 MBiOS主线程:1 MB |  异步线程堆栈的最小值是16 KB,并且堆栈大小是4 KB 的倍数。进程中线程创建的时候就会将这块记忆体腾出来,当时真正分页关联使用这块记忆体是在实际需要时候才会使用。

        <3>.创建时长(Creation time| 大约 90 毫秒 | 这个是创建线程到线程开始执行的时长, 这个数字与系统的CPU有关。


    3. 考虑的另外花销其实还有产品角度的, 设计一个多线程应用程序有时候需要更改数据结构, 对于一些设计比较差程序其维护更改会比较困难,因此设计数据结构和debug线程代码中的问题都会增加开发线程应用程序的时长。


(二)创建一个线程(Creating a Thread

    1. 使用NSThread

    使用 NSThread 类创建一个线程有两种方法:

        * 使用它的类方法: detachNewThreadSelector:toTarget:withObject:

        * 创建一个 NSThread 对象,调用起 start 方法。

    a. 两种方法创建的都是分离线程(detached thread),意味着线程退出的时候资源是被系统自动回收的,也意味着之后使用线程的代码不必显明的加入进来。

    b. 所有的OS X系统都有 detachNewThreadSelector:toTarget:withObject: 方法:

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

        参数:SEL是定义在target中的方法,object 是任何想要在线程踢动时候传过去的方法,targesel可能如下

        - voidmyThreadMainMethod(id)object

        {

            do  something wiht  object

        }

        这种方法,线程的创建和启动被封装在一个类方法中,不能或许线程属性或者对线程进行其他设置。

    c. NSThread 类的实例方法 detachNewThreadSelector:toTarget:withObject: ,声明和初始化一个线程后,其实线程没有真正创建所以也并不立刻执行,可以做线程做一些额外设置,然后通过 NSThread 的实例方法 start 正在创建并启动线程:

        NSThread* myThread = [[NSThread alloc] initWithTarget:self

                                             selector:@selector(myThreadMainMethod:)

                                               object:nil];

        [myThread start];  // Actually create the thread

        另一种使用detachNewThreadSelector:toTarget:withObject: 的方式是,可以继承NSThread 来重写它的main 方法,然后可以在main方法中实现自己线程的主接入点(main entry point)。

    d. 如果有一个正在运行的线程 NSThread 可以通过下面方法给它发送信息:(performSelector:onThread:withObject:waitUntilDone: ),这个方法可以被应用程序的任何兑现更直接使用。是线程间通信的一个方便的方法。对应的SEL将会被目标线程作为正产的run-loop进程来执行,也意味着目标线程必须是run-loop运行着的。还有一个与之同类的方法:(performSelector:onThread:withObject:waitUntilDone:


    2. 使用POSIX 线程(Using POSIX Threads

    它提供的是基于 C 线程创建的 API。可以被应用到任何应用程序(Cocoa Cocoa Touch 应用),并且可以是很方便的让软件支持多线程。创建线程函数:pthread_create   talk is cheap so  show the code

//Listing 2-1  Creating a thread in C

#include <assert.h>

#include <pthread.h>


void* PosixThreadMainRoutine(void* data)

{

    // Do some work here.


    return NULL;

}


void LaunchThread()

{

    // Create the thread using POSIX routines.

    pthread_attr_t  attr;

    pthread_t       posixThreadID;

    int             returnVal;


    returnVal = pthread_attr_init(&attr);

    assert(!returnVal);

    /// 因为POSIX 创建的线程默认是joinable的(non-detached 可内联线程,对应分离线程detached),所以这里更改了一下线程属性,传建一个分离线程(NSThread GCD 创建的都是detached)。

    ///分离线程的好处是在该线程退出的时候,系统可以自动回收资源。

    returnVal = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

    assert(!returnVal);


    int     threadError = pthread_create(&posixThreadID, &attr, &PosixThreadMainRoutine, NULL);


    returnVal = pthread_attr_destroy(&attr);

    assert(!returnVal);

    if (threadError != 0)

    {

        // Report an error.

    }

}

        a. 调用LaunchThread函数会创建一个心的分离线程,当时新线程不作任何事情,它加载启动之后几乎马上就会退出。不过可以将要完成的任务工作添加到PosixThreadMainRoutine 函数中,这样线程将知道该干啥了。在创建时候可以给传递一个data指针,并将指针给pthread_create方法的最后一个参数。

        b. 新建线程与应用程序的主线程通讯需要创建一个线程之间通讯的路径关系, 对于 基于 C 的应用程序有几种线程间通讯的方法,包括使用端口(ports),使用条件(conditions),或者共享内存。对于持久存在的线程,应该提供有线程间通讯的方法,让主线程能够检查线程的状态或者在应用程序退出的时候清理关闭它,


    3. 使用 NSObject 生成线程

        方法:performSelectorInBackground:withObject: ,它会创建一个新的分离线程,示例如下:

        [myObj performSelectorInBackground:@selector(doSomething) withObject:nil];

        a. myObj是一个实例化对象,SELdoSomething)是定义在myObj中的方法,会被在异步线程执行。

        b. 这个方法类似于在当前对象中使用 NSThread的类方法:(detachNewThreadSelector:toTarget:withObject:)方法一旦使用线程就会创建并开始执行,SEL中可能的操作有:启动一个 autorelease pool(如果不适用垃圾回收机制),或者如果需要可以设置该线程的 run loop


    4. Cocoas 应用中使用 POSIX 线程

        可以在 Cocoa 应用中使用 NSThread 类创建线程,也可以使用 POSIX ,如果应用中已经存在 POSIX 线程的代码,不想重写的话可以继续使用它, Cocoa 中是用 POSIX 需要注意一些Cocoa 线程交互之间 应该准守的原则:

        <1> 保护 Cocoa 框架,

            a. 对于多线程应用,Cocoa框架或使用锁 或者其它内部同步机制确保操作的正确执行,这个保护机制在 单线程的情况是不会启动的,如果应用程序使用到Cocoa 的方法启动了一个新的线程,Cocoa会知道,就会开启这个锁。但是如果使用POSIX 方法创建线程,Cocoa并不会收到通知知道想在应用是多线程的。这样就可能最终导致Cocoa框架引起的程序不稳定或者崩溃。

            b. Cocoa 注意到使用多线程的方法:只需要使用NSThread 启动一个线程然后让他即刻退出。线程入口不需要做任何事情,这样就能够确保Cocoa的锁(locks)就绪。

            c. 如果不能确定 Cocoa 是否知道当前应用是多线程环境,可以使用 NSThreadisMultiThreaded方法来验证。

        <2> 混淆 POSIX Cocoa Locks

            在同一个应用中使用 POSIX的互斥锁和 Cocoa locks 是安全的, Cocoa lock condition objects 本质是是对 POSIX 互斥锁和条件(conditions)的封装。当时注意,不能使用 Cocoa NSLock 对象去操作一个用pthread_mutex_init 方法创建的互斥锁。反之亦然。




(三)配置线程属性(Configuring Thread Attributes

    1. 为线程设置堆栈大小(Configuring the Stack Size of a Thread

        * 每一线程创建的时候,系统在进程控件中分配一个特定大小的内存去执行线程堆栈任务。堆栈会对堆栈帧以及其中线程声明的变量进行管理。如果想要该没变堆栈的大小,必须在创建线程之前做操作。所有的线程技术都提供有这样的方法。

        * 堆栈大小设置方法:

        <1> Cocoa | 初始化 NSThread对象,在调用 start 方法之前,使用(setStackSize:)更新堆栈大小,这个意味着不能使用(detachNewThreadSelector:toTarget:withObject:

        <2> POSIX | 穿件一个新的pthread_attr_t 结构,用 pthread_attr_setstacksize 方法更爱默认堆栈大小,最后传入 pthread_create函数中创建新线程。

        <3> Multiprocessing Services | MPCreateTask  (i  remember , let me forget it )


    2. 线程内建存储设置(Configuring Thread-Local Storage

        * 每一个线程内部都维持有一个key-value的字典,可以在线程的任何地方获取到,可以用这个字典来存储一些想要在整个线程周期里面都用到的数据。如可以存数据用在线程run loop的大量迭代中。

        * Cocoa POSIX 存储线程字典的方式不一样,

        a. Cocoa 中,可以使用一个 NSThread 对象调用  threadDictionary 方法来存储一个可NSMutableDictionary 对象。(它是 NSThread的一个属性成员 @property(readonly, retain) NSMutableDictionary *threadDictionary

        b. 对于POSIX ,使用pthread_setspecific pthread_getspecific 函数来设置获取线程的 keys values


    3. 设置线程的分离状态(Setting the Detached State of a Thread

        * 大多比较高级别的线程技术默认创建的是 分离线程,大多数时候,这是优选,因为这样在线程完成任务退出之后,系统可以自动释放线程持有的数据,分离线程也不需要显明的与程序进行交互,以为着线程的运行结果任你处置。但是,系统不会自动回收一个可内联的线程(joinable 或者 non-detached)资源,除非有一个线程显明的与他内联,进程有可能阻塞内联的线程。

        * 可以吧可内联的线程当做一个孩子线程,(尽管它们还是各自运行),一个内联线程必须在它的资源可以别系统自动回收前与另一个线程发生内联关系,内联线程也提供了显明的方法从一个正准备退出的线程传递数据给另一个线程(退出前完成)。内联线程可以使用函数pthread_exit 传递数据指针或者其他的值,另一个线程可以使用函数 pthread_join 取得这些数据。

        * 注意:应用程序退出的时候,分离线程会被立刻终结,但是内联线程不会,它之前需要发生内联,它的特征适合于处理一些关键的不能被打断的任务,比如给磁盘写入数据。

        * 想要创建可内联线程,唯一方法是使用 POSIX 线程,POSIX 默认创建的线程就是 可内联的,可以将一个线程标明是分离或者可内联的,可以在创建线程之前更改线程属性,使用方法:( pthread_attr_setdetachstate)。线程开启后,可以用方法 pthread_detach 将一个joinable线程转为detached线程, 线程内联方法 see thd pthread_join man page


    4. 设置线程优先级(Setting the Thread Priority

        * 新线程创建的时候会有一个默认的优先级, 通常默认是内核(kernel)的调度算法搞定,优先级高的自然相对更容易获得执行机会,不过执行次数也是不保证的。

        * 注意:通常来说线程使用默认的优先级就好啦。如果更改了一个线程优先级跳高,相对就会有其他线程优先级变得底了,打破了平衡,此时如果低优先级与其某高优先级的线程存在交互,那么可能因为低优先级的阻塞问题影响到程序的顺利执行。

        * Cocoa POSIX 都有方法更改线程优先级, Cocoa 可以 调用 NSThread 类的  setThreadPriority: 方法。 对于POSIX,可以使用 pthread_setschedparam 函数实现。


(四)编写线程的入口例程(Writing Your Thread Entry Routine(MRC 情况)


    1. 创建自动释放池(Creating an Autorelease Pool

    * MRC 中,一个新线程中入口例程要做的第一件事情就是创建一个自动释放池,最后一件事情是释放这个释放池对象。

    - (void)myThreadMainRoutine

    {

    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Top-level pool


    // Do thread work here.


    [pool release];  // Release the objects in the pool.

    }

    * 然而如今使用的是ARC 自动管理,因此不能使用NSAutoreleasePool 对象了,其实也没必要一定需要手动添加释放池子,如果想要添加,可以使用Block方法: @@autoreleasepool { }


    2. 开启一个异常处理(Setting Up an Exception Handler

        @try/@catch/@finally , 可以在 Xcode中使用 OC 或者 C++ 的异常处理方法。


    3. 开启一个运行池(Setting Up a Run Loop

        * 为一个新的线程写代码,有两种情况,

        <1>. 线程执行的任务时候没有任何干扰或者只会出现很少的打断,这时候只需要让线程自然执行方法,完了之后线程退出,这种方法不需要添加除任务之外的任何代码,只需做想做的就行。

        <2>. 线程需要进入一个池子,有自己的进程动态要求,这时候就需要写入启动线程的运行池子的方法。

        <3>. OS 系列的系统中都为每一个线程内建有它的运行池子,并且app 框架中自动启动了应用主线程的池子。但是对于新建的其他线程,则需要手动设置池子然后手动启动它。


(五)终结一个线程(Terminating a Thread

    * 推挤退出线程的方法就是,让它执行完毕之后,按例程自动退出。使用方法(尽管Cocoa|POSIX|Multiprocessing Services都有方法)强制让线程退出是不鼓励的。强制退出会阻碍线程的自我清理,被线程开辟的内存可能发生泄漏,还有其他的一些被线程使用的资源可能没有被合理的清理,在之后的进程中导致问题潜留。

    * 如果逾期需要在一个执行任务期间终结线程,应该为线程设计一个信息提示机制,对于持久运行的任务,可能就是定期的停止工作,让后检查是否与这样的信息抵达,如果信息抵达要求退出线程,则线程此时就有机会处理清理工作并优雅的退出;否则,可以让它简单的回去工作并处理下一块数据。

    * 用池子输入源接受消息是一个相应这种消息的方法:

//Listing 2-3  Checking for an exit condition during a long job

- (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];

    }

}



总结:

    1. 介绍了 OS 系列中使用到线程的技术,基本上都有提到了,

    2. NSOperation 相关 介绍不多,GCD 介绍不多,主要是NSThread的常用方法都差不多了

    3. 本篇文档的要义还在于传播一种 线程 管理的思想,对于API接口的讲解略少提及, 反而可以让思绪集中在从运行逻辑原理机制上考虑。

    4. keep going ~ form here//https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/CreatingThreads/CreatingThreads.html#//apple_ref/doc/uid/10000057i-CH15-SW2




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值