IOS多线程编程指南一之线程

2018-03-19  本文已影响25人  leonardni

文章结构:
1.什么是线程

一、什么是线程


From a technical standpoint, a thread is a combination of the kernel-level and application-level data structures needed to manage the execution of code. The kernel-level structures coordinate the dispatching of events to the thread and the preemptive scheduling of the thread on one of the available cores. The application-level structures include the call stack for storing function calls and the structures the application needs to manage and manipulate the thread’s attributes and state.
从技术的实现的角度是结合内核层和应用层的结果。内核负责线程事件(event)的分派和线程优先级的调度,应用层则负责存储线程函数中断时的状态和属性的存储方便下次内核切换时再从存储的地方运行。搞过底层的人看过内核原理的人就清楚,多线程其实是软件模拟硬件中断的过程。通过一定时间内快速切换函数并保存被切函数在cpu栈内运行的数据,下次切换回来的时候又再将保存的数据写入到cpu中,切换速度快了就像多个任务同时在运行。还理解不了可以想下,多任务下载的情景。

官方文档里举了个实际的例子(具体例子就不细说了),介绍了下多线程给应用带来的好处。同时也提醒了多线程可能带来的1.增加了程序的复杂性 2.多个线程共享同样的内存空间,多个线程同时访问同一个数据时可能带来数据安全问题。即使是对相关的数据进行了保护,编译器的优化也会带来的意想不到的错误。

1.1 几个专业术语

thread 单独处理的代码
process 正在执行的任务(内部有多个线程)
task 任务的抽象概念

为什么要理解线程官方是这么说的:
If you do not fully understand the implications of your design choices, you could easily encounter synchronization or timing issues, the severity of which can range from subtle behavioral changes to the crashing of your application and the corruption of the user’s data.
很容易遇到同步问题或者时间问题,造成你的应用闪退或者毁坏你的数据。

几个方案

NSOperation GCD Idle-time notifications

线程三种状态:
After starting a thread, the thread runs in one of three main states: running, ready, or blocked.
当线程函数返回或者线程被显示的终止,线程讲永远终止并且系统会回收线程所占用的内存空间和时间。

1.3 Runloop

1.4 线程同步工具

多线程在访问同一个数据时候会带来数据安全性问题。

  1. One way to alleviate the problem is to eliminate the shared resource altogether and make sure each thread has its own distinct set of resources on which to operate.
  2. When maintaining completely separate resources is not an option though, you may have to synchronize access to the resource using locks, conditions, atomic operations, and other techniques.
    官方提出几种方式:
  1. locks 2.conditions 3,atomic

locks 当一个线程A尝试访问另外一个线程B正在使用的数据时,线程A会阻塞直到线程B释放对数据的使用。
conditions 相当于一个看门者
A condition acts as a gatekeeper, blocking a given thread until the condition it represents becomes true. When that happens, the condition releases the thread and allows it to continue.
atomic 线程安全,是lock轻量型的一种实现方式,通过硬件指令的方式来保证数据的安全。

1.5 线程内通信

方式 例子
Direct messaging 直接消息调用 - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array
Global variables, shared memory, and objects 全局变量或者共享内存空间/对象
Conditions 看门者
Run loop sources
Ports and sockets
Message queues 不推荐
Cocoa distributed objects 不推荐

1.6 多线程序开发指导建议

1.6.1 Avoid Creating Threads Explicitly

避免直接创建thread可以使用GCD 或者 NSOperation 的方式代替。

Rather than create a thread yourself, consider using asynchronous APIs, GCD, or operation objects to do the work. These technologies do the thread-related work behind the scenes for you and are guaranteed to do it correctly. In addition, technologies such as GCD and operation objects are designed to manage threads much more efficiently than your own code ever could by adjusting the number of active threads based on the current system load.

1.6.2 Keep Your Threads Reasonably Busy

尽量使你的线程是高效的,尽可能终止大部分时间处于闲置状态的线程。
因为线程会消耗当前系统的资源。

1.6.3 Avoid Shared Data Structures
1.6.4 Threads and Your User Interface

If your application has a graphical user interface, it is recommended that you receive user-related events and initiate interface updates from your application’s main thread.
建议把用户相关的事件(比如触摸事件处理)和图形交互的更新放在主线程,这里官方提到一个例子:如图形的处理解码和运算可以放到其他线程然后在主线程更新。

1.6.5 Be Aware of Thread Behaviors at Quit Time

A process runs until all non-detached threads have exited. By default, only the application’s main thread is created as non-detached, but you can create other threads that way as well.
When the user quits an application, it is usually considered appropriate behavior to terminate all detached threads immediately, because the work done by detached threads is considered optional. If your application is using background threads to save data to disk or do other critical work, however, you may want to create those threads as non-detached to prevent the loss of data when the application exits.
process 将会退出直到内不没有运行的线程,正常来说应用退出后系统会立刻停止应用的所有线程,因为系统认为他们是非必要的。但通常来说我们都希望在程序即将退出的时候进行一些重要数据的存储和必要的一些操作。
可以通过两种方式来实现

(1). 创建一个non-detached的线程
需要使用POSIX API相关函数来将你需要运行的线程join到主线程中。具体的见 Setting the Detached State of a Thread

(2). IOS下可以使用applicationShouldTerminate: delegate
来延迟应用这个过程,等相关的线程执行完其任务后再调用replyToApplicationShouldTerminate:函数退出。

1.6.6 Handle Exceptions(处理异常)

每个线程有自己的call stack队列,因此异常处理需要自身处理。 捕获的异常可以通过消息的方式发给其他线程,抛出异常的线程1.继续运行(可能的话) 2.等待指令 3.直接退出
1.6.7 Terminate Your Threads Cleanly

最好的方式是让线程的函数执行完,自动释放其所占用的系统支援。
第二种方式是代码的方式退出线程,这种可能会导致线程申请的内存空间和相关的资源无法释放,或其他问题。

1.6.8 Thread Safety in Libraries

SDK 开发者在开发SDK时,应考虑多线程引起的数据安全情况。

二、线程管理:


When an application spawns a new thread, that thread becomes an independent entity inside of the application's process space. Each thread has its own execution stack and is scheduled for runtime separately by the kernel. A thread can communicate with other threads and other processes, perform I/O operations, and do anything else you might need it to do. Because they are inside the same process space, however, all threads in a single application share the same virtual memory space and have the same access rights as the process itself.

  1. 线程创建后便是一个独立的个体,享有自己独立的属执行堆栈和内核的执行安排。
  2. 应用中所有线程共享相同的虚拟的内存空间,并且对这块虚拟空间享有相同的权限。

2.1 Thread Costs

2.2 创建线程

2.2.1 NSThread

#import "customThread.h"

@implementation customThread
-(void)main{
    NSLog(@"current thread = %@",[NSThread currentThread]);
}
@end

已经运行的线程可以通过performSelector:onThread:withObject:waitUntilDone:方法来向线程发送消息,该函数亦可以用来做线程通信(不推荐),目标线程需要运行在runloop模式下。

2.2.2 POSIX Threads

C函数,pthread 类,暂时没有配好混编环境,没办法跑官方demo;

2.2.3 使用NSObject方法创建线程

使用performSelectorInBackground函数

- (void)createThreadUseObjectAPI{
    [self performSelectorInBackground:@selector(thread3Func) withObject:nil];
}

使用performSelectorInBackground创建的线程会使用默认的配置,立即执行,不需要人为的start操作。

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

2.3.1 配置stack大小

配置的内存单位是byte为单位,且必须是4K的倍数。

2.3.2 Configuring Thread-Local Storage

Each thread maintains a dictionary of key-value pairs that can be accessed from anywhere in the thread.
每个线程都含有一个字典对象来保存键值对。

类型 函数
NSThread threadDictionary
POSIX use pthread_setspecific and pthread_getspecific functions to set and get the keys and values of your thread.

2.3.3 Setting the Detached State of a Thread(设置线程的状态)

默认创建的线程为detached thread,这样的线程执行完毕后,线程相关的资源会被系统回收。
相比之下,还有种线程的类型即(joinable thread),线程的资源是不会被系统回收的,直到有新的线程调用pthread_join函数join到该线程(join的过程process会阻塞该线程)。

Joinable threads also provide an explicit way to pass data from an exiting thread to another thread. Just before it exits, a joinable thread can pass a data pointer or other return value to the pthread_exit function.

如何创建joinable线程

If you do want to create joinable threads, the only way to do so is using POSIX threads. POSIX creates threads as joinable by default. To mark a thread as detached or joinable, modify the thread attributes using the pthread_attr_setdetachstate function prior to creating the thread. After the thread begins, you can change a joinable thread to a detached thread by calling the pthread_detach function. For more information about these POSIX thread functions, see the pthread man page. For information on how to join with a thread, see the pthread_join man page.

2.3.4 Setting the Thread Priority(设置线程的优先级)

函数
NSThread setThreadPriority:
POSIX threads pthread_setschedparam

官方建议尽量少调整线程的优先级,因为这样极有可能会导致优先级低的线程一直没法运行。另一种情况:当你的应用包含优先级高和优先级的线程并两种级别的线程存在交互时,优先级低(优先级低得不到内核的调度)的线程会阻塞级别高的交互线程,造成性能瓶颈

2.3.5 Writing Your Thread Entry Routine 线程的入口函数

(1)Creating an Autorelease Pool

- (void)myThreadMainRoutine{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Top-level pool
    // Do thread work here.
    [pool release];  // Release the objects in the pool.
}

创建一个自动释放池来管理线程的对象,在线程的入口函数开始时创建,线程结束前释放,有助于及早释放线程函数所占用的内存空间,提高系统的性能。

(2)Setting Up an Exception Handler(设置相关的异常处理)

If your application catches and handles exceptions, your thread code should be prepared to catch any exceptions that might occur. Although it is best to handle exceptions at the point where they might occur, failure to catch a thrown exception in a thread causes your application to exit.
详细的见: Exception Programming Topics.

设置相关的异常处理,因为不可捕获的异常将会导致程序退出。

(2)Setting Up a Run Loop

OS X and iOS provide built-in support for implementing run loops in every thread. The app frameworks start the run loop of your application’s main thread automatically. If you create any secondary threads, you must configure the run loop and start it manually. OS X and iOS provide built-in support for implementing run loops in every thread. The app frameworks start the run loop of your application’s main thread automatically. If you create any secondary threads, you must configure the run loop and start it manually.

2.4 Terminating a Thread

The recommended way to exit a thread is to let it exit its entry point routine normally.
官方建议退出线程的最好方式是让线程执行完线程相关的函数自然退出,因为强行退出线程,将会引起线程使用的内存无法释放问题即内存泄漏问题。
如果想强行退出线程,可以通过间歇性的检查退出信号,当接受到退出信号时,执行必要的引用内存释放工作,避免因退出线程引起的内存泄漏问题。

一种方式就是通过runloop 自定义 input source的方式实现。
大体的函数如下:

- (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这个值是否被input source处理函数修改。如果是退出线程。
        exitNow = [[threadDict valueForKey:@"ThreadShouldExitNow"] boolValue];
    }
}

官方给出了大概的实现逻辑,这里应该还需要在while循环开始和结束处添加auto releasePool。

参考文献:

官方文档:Threading Programming Guide

上一篇下一篇

猜你喜欢

热点阅读