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

文章结构:
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

  • Use the detachNewThreadSelector:toTarget:withObject: class method to spawn the new thread.

  • Create a new NSThread object and call its start method.

  • 继承NSThread 复写 实例方法 -(void)main;

#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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容

  • rljs by sennchi Timeline of History Part One The Cognitiv...
    sennchi阅读 7,279评论 0 10
  • 我们拥有同样的夜色 在同一片夜空下 亦或是在一个门廊里 言语、呼吸。 但我的夜,却一定不是你的夜。 我的夜 在我所...
    耕芸的芸阅读 160评论 3 3
  • 2017赛季中超联赛在本周末重燃战火,激烈的比赛、大牌的球星,还有版本更新带来的新博弈。 版本更新是IT词汇,特别...
    清水一点通阅读 133评论 0 0
  • 昨晚卖的不好,种种原因,总结一下归纳一下几点:1 被动 要主动吆喝,发宣传页,让顾客品尝~~只有品尝了顾客才会买,...
    憧憬幸福阅读 106评论 0 0
  • 无论是现在繁复手艺的制造业,还是走在时代前端的移动互联网行业,“工匠精神”都成为了人们热衷于谈论与标榜的标签,到底...
    红色经典款阅读 305评论 0 0