4.1/4.2 多线程进阶篇<上>(Pthread & NSThread)

本文并非最终版本,如有更新或更正会第一时间置顶,联系方式详见文末

如果觉得本文内容过长,请前往本人 “简书

本文源码 Demo 详见 Github
https://github.com/shorfng/iOS-4.0-multithreading.git

 

因为Pthread很少用到,所以对于Pthread的知识没有抠那么细致,所以将Pthread和 NSThread放在了一起。

 

4.1 Pthread


4.1-1.0 创建线程 - pthread_create

 1 /*
 2 <#pthread_t *restrict#>  线程的id,指向线程标识符的指针,C 语言中类型的结尾通常 _t/Ref,而且不需要使用 *
 3 <#const pthread_attr_t *restrict#> 用来设置线程的属性 (一般为 NULL)
 4 <#void *(*)(void *)#>  新建立的线程执行代码的函数,线程开启后需要调用的函数或方法 (指向函数的指针)
 5 <#void *restrict#>     运行函数的参数,线程的限制 (一般为 NULL)
 6 */
 7 
 8 返回值:
 9  - 若线程创建成功,则返回0
10  - 若线程创建失败,则返回出错编号
11 
12 pthread_create(
13           <#pthread_t *restrict#>,   // pthread_t :线程标识符.
14           <#const pthread_attr_t *restrict#>,
15           <#void *(*)(void *)#>,
16           <#void *restrict#>
17 );

 

4.1-1.1 __bridge 桥接

__bridge 桥接:
1、在c语言和OC之间,对数据类型进行转成换
2、通过桥接告诉c语言的函数,name就由C语言去管了
桥接的目的 : 
就是为了告诉编译器如何管理内存,为OC添加自动内存管理操作
 
小结 :
  • 在 C 语言中,没有对象的概念,对象是以结构体的方式来实现的
  • 通常,在 C 语言框架中,对象类型以 _t/Ref 结尾,而且声明时不需要使用 *
  • C 语言中的 void * 和 OC 中的 id 是等价的
  • 在混合开发时,如果在 C 和 OC 之间传递数据,需要使用 __bridge 进行桥接,
  • 桥接的添加可以借助 Xcode 的辅助功能添加
 
number = 1: 表示 主线程
number != 1: 表示 子线程
 
C语言中 void * == OC中的id
C语言的数据类型,一般以  Ref / _t
  • OC框架 Foundation
  •   C语言 Core Foundation
 
 4.1-1.2【代码】Pthread
 1 首先导入头文件
 2 
 3 #import <pthread.h>
 4 
 5 代码创建:
 6 
 7 // 创建线程,并且在线程中执行 demo 函数
 8 - (void)pthreadDemo {
 9 
10   pthread_t threadId = NULL;
11   NSString *str = @"Hello Pthread";
12 
13   int result = pthread_create(&threadId, NULL, demo, (__bridge void *)(str));
14 
15   if (result == 0) {
16     NSLog(@"创建线程 OK");
17   } else {
18     NSLog(@"创建线程失败 %d", result);
19   }
20 }
21 
22 // 后台线程调用函数
23 void *demo(void *params) {
24   NSString *str = (__bridge NSString *)(params);
25 
26   NSLog(@"%@ - %@", [NSThread currentThread], str);
27 
28   return NULL;
29 }

 

 

4.2  NSThread


 

4.2-1.0 创建线程

  • 第一种:通过NSThread的对象方法 (alloc / init - start)
  • 第二种:通过NSThread的类方法    (detachNewThreadSelector)
  • 第三种:通过NSObject的方法
 4.2-1.1 创建线程1 - 对象方法alloc/init
一个NSThread对象就代表一条线程
1 创建方式1 : 通过NSThread的对象方法 (先创建初始化线程alloc/init , 再 start 开启线程)   ——调试方便
2 
3 NSThread *thread = [[NSThread alloc]initWithTarget:<#(nonnull id)#>
4                                           selector:<#(nonnull SEL)#>
5                                             object:<#(nullable id)#> ];
 1 #import "ViewController.h"
 2 
 3 @interface ViewController ()
 4 @end
 5 
 6 @implementation ViewController
 7 
 8 - (void)viewDidLoad {
 9   [super viewDidLoad];
10   // Do any additional setup after loading the view, typically from a nib.
11 }
12 
13 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
14   [self threadDemo1];
15 }
16 
17 #pragma mark - 对象方法alloc/init
18 /*
19  - 在 OC 中,任何一个方法的代码都是从上向下顺序执行的
20  - 同一个方法内的代码,都是在相同线程执行的(block除外)
21  */
22 - (void)threadDemo1 {
23   NSLog(@"before %@", [NSThread currentThread]);
24 
25   // 线程一启动,就会在线程thread中执行self的run方法
26   NSThread *thread = [[NSThread alloc] initWithTarget:self
27                                              selector:@selector(longOperation:)
28                                                object:@"THREAD"];
29 
30   //开启线程,通过start方法,就会将我们创建出来的当前线程加入到`可调度线程池`,供CPU调度
31   //[thread start];执行后,会在另外一个线程执行 longOperation: 方法
32   [thread start];
33 
34   NSLog(@"after %@", [NSThread currentThread]);
35 }
36 
37 - (void)longOperation:(id)obj {
38   NSLog(@"%@ - %@", [NSThread currentThread], obj);
39 }
40 
41 - (void)didReceiveMemoryWarning {
42   [super didReceiveMemoryWarning];
43   // Dispose of any resources that can be recreated.
44 }
45 
46 @end
打印结果:

2016-03-17 18:19:41.878 创建线程1 - 对象方法[2543:387435] before <NSThread: 0x7ffd28c00ca0>{number = 1, name = main}
2016-03-17 18:19:41.880 创建线程1 - 对象方法[2543:387711] <NSThread: 0x7ffd28d77be0>{number = 2, name = (null)} - THREAD
2016-03-17 18:19:41.880 创建线程1 - 对象方法[2543:387435] after <NSThread: 0x7ffd28c00ca0>{number = 1, name = main}

  

4.2-1.1.1 Target

NSThread 的实例化方法中的 target 指的是开启线程后,在线程中执行 哪一个对象 的 @selector 方法
1 NSThread *thread = [[NSThread alloc] initWithTarget:self.person
2                                            selector:@selector(longOperation:)
3                                              object:@"THREAD"];
4 
5 [thread start];
  • 通过指定不同的 target 会在后台线程执行该对象的 @selector 方法
  • 不要看见 target 就写 self

 

4.2-1.2 创建线程2 - 类方法
1 创建方式2 : 通过NSThread的类方法 (创建线程后直接自动启动线程)
2 
3 [NSThread detachNewThreadSelector:<#(nonnull SEL)#>
4                          toTarget:<#(nonnull id)#>
5                        withObject:<#(nullable id)#> ];
 1 #import "ViewController.h"
 2 
 3 @interface ViewController ()
 4 @end
 5 
 6 @implementation ViewController
 7 
 8 - (void)viewDidLoad {
 9   [super viewDidLoad];
10   // Do any additional setup after loading the view, typically from a nib.
11 }
12 
13 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
14   [self threadDemo2];
15 }
16 
17 #pragma mark - 类方法
18 - (void)threadDemo2 {
19   NSLog(@"before %@", [NSThread currentThread]);
20 
21   // detachNewThreadSelector 类方法不需要启动,会自动创建线程并执行@selector方法
22   // 它会自动给我们做两件事 :  1.创建线程对象  2.添加到`可调度线程池`
23   // 通过NSThread的类和NSObject的分类方法直接加入到可调度线程池里面去,等待CPU调度
24   [NSThread detachNewThreadSelector:@selector(longOperation:)
25                            toTarget:self
26                          withObject:@"DETACH"];
27 
28   NSLog(@"after %@", [NSThread currentThread]);
29 }
30 
31 - (void)longOperation:(id)obj {
32   NSLog(@"%@ - %@", [NSThread currentThread], obj);
33 }
34 
35 - (void)didReceiveMemoryWarning {
36   [super didReceiveMemoryWarning];
37   // Dispose of any resources that can be recreated.
38 }
39 
40 @end
打印结果:

2016-03-17 18:36:05.339 创建线程2 - 类方法[2647:404930] before <NSThread: 0x7fddf8f01eb0>{number = 1, name = main}
2016-03-17 18:36:05.340 创建线程2 - 类方法[2647:404930] after <NSThread: 0x7fddf8f01eb0>{number = 1, name = main}
2016-03-17 18:36:05.340 创建 线程2 - 类方法[2647:405061] <NSThread: 0x7fddf8e0e7a0>{number = 2, name = (null)} - DETACH

 

4.2-1.3 创建线程3 - 分类方法(NSObject)

1 创建方式3 : 通过NSObject的分类方法  (隐式创建并直接自动启动线程)   ——推荐,开发常用
2 
3 // 此方法在后台线程中执行 (即是 : 在子线程中执行)
4 [self performSelectorInBackground:<#(nonnull SEL) #>
5                        withObject:<#(nullable id) #> per];
 1 #import "ViewController.h"
 2 
 3 @interface ViewController ()
 4 @end
 5 
 6 @implementation ViewController
 7 
 8 - (void)viewDidLoad {
 9   [super viewDidLoad];
10   // Do any additional setup after loading the view, typically from a nib.
11 }
12 
13 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
14   [self threadDemo3];
15 }
16 
17 #pragma mark - 分类方法
18 - (void)threadDemo3 {
19   NSLog(@"before %@", [NSThread currentThread]);
20 
21   // performSelectorInBackground 是NSObject的分类方法,会自动在后台线程执行@selector方法
22   // 没有 thread 字眼,隐式创建并启动线程
23   // 所有 NSObject 都可以使用此方法,在其他线程执行方法
24   // 通过NSThread的类和NSObject的分类方法直接加入到可调度线程池里面去,等待CPU调度
25   // PerformSelectorInBackground 可以让方便地在后台线程执行任意NSObject对象的方法
26   [self performSelectorInBackground:@selector(longOperation:)
27                          withObject:@"PERFORM"];
28 
29   NSLog(@"after %@", [NSThread currentThread]);
30 }
31 
32 - (void)longOperation:(id)obj {
33   NSLog(@"%@ - %@", [NSThread currentThread], obj);
34 }
35 
36 - (void)didReceiveMemoryWarning {
37   [super didReceiveMemoryWarning];
38   // Dispose of any resources that can be recreated.
39 }
40 
41 @end
打印结果:

2016-03-17 18:41:58.696 创建线程3 - 分类方法[2711:412522] before <NSThread: 0x7ff078c02320>{number = 1, name = main}
2016-03-17 18:41:58.696 创建线程3 - 分类方法[2711:412522] after <NSThread: 0x7ff078c02320>{number = 1, name = main}
2016-03-17 18:41:58.697 创建线程3 - 分类方法[2711:412751] <NSThread: 0x7ff078c0e390>{number = 2, name = (null)} - PERFORM

  

方式2和方式3的优缺点 :
优点:简单快捷
缺点:无法对线程进行更详细的设置

 

4.2-2.0 线程属性

 1 主线程相关方法 :
 2 
 3 + (NSThread *)mainThread; // 获得主线程
 4 - (BOOL)isMainThread; // 是否为主线程
 5 + (BOOL)isMainThread; // 是否为主线程
 6 
 7 NSThread *main = [NSThread mainThread];   // + (NSThread *)mainThread;  获得主线程
 8 
 9 [NSThread  isMainThread]; //  + (BOOL)isMainThread;    类方法判断,该方法是否为主线程
10 
11 [main isMainThread];      //  - (BOOL)isMainThread;   对象方法判断,该对象是否为主线程
 1 其他用法:
 2 (1) currentThread - 获得当前线程 :
 3 
 4 举例 :
 5 NSThread *current = [NSThread currentThread]; //获得当前线程
 6 
 7 (2) threadPriority - 线程的调度优先级 :
 8 
 9 优先级,是一个浮点数,取值范围从 0~1.0 默认优先级是0.5 值越大,优先级越高
10 
11 优先级高只是保证 CPU 调度的可能性会高
12 
13 建议:在开发的时候,不要修改优先级
14 多线程的目的:是将耗时的操作放在后台,不阻塞主线程和用户的交互!
15 多线程开发的原则:简单
16 
17 //返回当前方法所在线程的优先级
18 + (double)threadPriority;
19 举例:[NSThread threadPriority];
20 
21 //设置线程的优先级
22 + (BOOL)setThreadPriority:(double)p;
23 举例:self.thread1.threadPriority = 1.0;
24 
25 - (double)threadPriority;//返回当前方法所在线程的优先级
26 - (BOOL)setThreadPriority:(double)p;//设置线程的优先级
27 
28 (3) name - 线程的名字 : 在大的商业项目中,通常需要在程序崩溃时,获取程序准确执行所在的线程
29 
30 - (void)setName:(NSString *)n;   //set 方法
31 - (NSString *)name;              //get 方法
32 
33 举例:
34 thread.name = @"线程A";
35 
36 (4) stackSize - 栈区大小
37 
38 - 默认情况下,无论是主线程还是子线程,栈区大小都是 512K
39 - 栈区大小虽然可以设置,但是我们一般都使用系统默认的大小就行了
40 
41 举例:
42 [NSThread currentThread].stackSize = 1024 * 1024;

代码示例:

 1 // 线程属性
 2 - (void)threadProperty {
 3   NSThread *t1 = [[NSThread alloc] initWithTarget:self
 4                                          selector:@selector(demo)
 5                                            object:nil];
 6 
 7   // 1. 线程名称
 8   t1.name = @"Thread AAA";
 9 
10   // 2. 优先级
11   t1.threadPriority = 0;
12 
13   [t1 start];
14 
15   NSThread *t2 = [[NSThread alloc] initWithTarget:self
16                                          selector:@selector(demo)
17                                            object:nil];
18   // 1. 线程名称
19   t2.name = @"Thread BBB";
20   // 2. 优先级
21   t2.threadPriority = 1;
22 
23   [t2 start];
24 }
25 
26 - (void)demo {
27   for (int i = 0; i < 10; ++i) {
28     // 堆栈大小
29     NSLog(@"%@ 堆栈大小:%tuK", [NSThread currentThread],[NSThread currentThread].stackSize / 1024);
30   }
31 
32    // 模拟崩溃
33    // 判断是否是主线程
34       if (![NSThread currentThread].isMainThread) {
35           NSMutableArray *a = [NSMutableArray array];
36           [a addObject:nil];
37       }
38 }

 

4.2-3.0 线程状态/线程生命周期 

  • 新建状态
  • 就绪状态/启动状态 : 线程在可调度线程池中
  • 运行状态
  • 阻塞状态/暂停线程 : 线程不在可调度线程池中,但是仍然存在内存中,只是不可用
  • 死亡状态 : 线程不在内存中
 
(1)新建状态 : 实例化线程对象
说明:创建线程有多种方式,这里不做过多的介绍
 
 
(2)就绪状态 / 启动线程: ( 进入就绪状态 ->运行状态。当线程任务执行完毕,自动进入死亡状态 )
线程开启 : 线程进入可调度线程池
 
  • 向线程对象发送 start 消息,线程对象被加入可调度线程池等待 CPU 调度
  • detachNewThreadSelector 方法和 performSelectorInBackground 方法会直接实例化一个线程对象并加入可调度线程池

(3)运行状态:
  • CPU 负责调度可调度线程池中线程的执行
  • 线程执行完成之前(死亡之前),状态可能会在就绪和运行之间来回切换
  • 就绪和运行之间的状态变化由 CPU 负责,程序员不能干预
 
  • 当 CPU 调度当前线程 , 进入运行状态
  • 当 CPU 调度其他线程 , 进入就绪状态
(4)阻塞状态 / 暂停线程 : 当满足某个预定条件时,可以使用休眠或锁阻塞线程执行
 1 方法执行过程,符合某一条件时,可以利用 sleep 方法让线程进入 阻塞 状态
 2 
 3 sleepForTimeInterval:    //休眠指定时长    (从现在起睡多少秒)
 4 sleepUntilDate:          //休眠到指定日期 (从现在起睡到指定的日期)
 5 @synchronized(self) { }   //互斥锁
 6 
 71)阻塞2秒
 8 [NSThread sleepForTimeInterval:2]; // 阻塞状态
 9 
102)以当前时间为基准阻塞4秒
11 NSDate *date = [NSDate dateWithTimeIntervalSinceNow:4.0]; //从现在开始多少秒
12 [NSThread sleepUntilDate:date];  //睡眠多少秒

(5)死亡状态 (一旦线程停止或死亡了,就不能再次开启任务 , 后续的所有代码都不会被执行 )
(1) 正常死亡
  • 线程执行完毕
 
(2) 非正常死亡
  • (自杀)          当满足某个条件后,在线程内部自己中止执行
  • (被逼着死亡) 当满足某个条件后,在主线程给其它线程打个死亡标记(下圣旨),让子线程自行了断.
 
注意:在终止线程之前,应该注意释放之前分配的对象! 
 
 生命周期示意图:
 
 
 代码示例:
 1 #import "ViewController.h"
 2 
 3 @interface ViewController ()
 4 @end
 5 
 6 @implementation ViewController
 7 
 8 - (void)viewDidLoad {
 9   [super viewDidLoad];
10   // Do any additional setup after loading the view, typically from a nib.
11 }
12 
13 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
14 
15   // 1.在主线程中创建一个子线程(实例化线程对象) ---> 新建状态
16   NSThread *Th =
17       [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
18 
19   // 2.将 Th 线程加入到可调度线程池,等待CPU调度--->就绪状态
20   [Th start];
21 
22   // 3.让主线程阻塞,让当前线程(主线程)休眠
23   [NSThread sleepForTimeInterval:1.0];
24 
25   // 4.在主线程给 Th 线程打死亡标签
26   [Th cancel]; //只是打了个标签,并没有执行,需要在子线程中
27 }
28 
29 // Th 线程---> 运行状态
30 - (void)run {
31 
32   NSThread *huThread = [NSThread currentThread];
33 
34   CGMutablePathRef path = CGPathCreateMutable();
35 
36   for (int i = 0; i < 30; i++) {
37     if ([huThread isCancelled]) {
38       NSLog(@"good bye1");
39       return; // --->非正常死亡(被逼着死亡)
40     }
41 
42     if (i == 5) {
43       [NSThread sleepForTimeInterval:3.0]; //--->huThread阻塞状态3秒
44       // [NSThread sleepUntilDate:[NSDate distantFuture]]; // 睡到遥远的未来
45       // [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]]; //线程睡到从现在开始后的2秒为止
46     }
47 
48     if ([huThread isCancelled]) {
49       NSLog(@"good bye2");
50       return;
51     }
52 
53     if (i == 20) {
54       //清空资源
55       CGPathRelease(path);
56 
57       //在调用下面方法之前,必须清空资源  非正常死亡--自杀(退出线程)
58       [NSThread exit];
59     }
60 
61     if ([huThread isCancelled]) {
62       NSLog(@"good bye3");
63       return;
64     }
65     NSLog(@"%d", i);
66   }
67 } //--->huThread死亡状态  (正常死亡状态)
68 
69 - (void)didReceiveMemoryWarning {
70   [super didReceiveMemoryWarning];
71   // Dispose of any resources that can be recreated.
72 }
73 
74 @end

 

4.2-4.1 多线程安全隐患 - 资源共享/抢夺

(1) 起因 : 
 
资源共享概念 : 1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源
 
主要是因为多条线程,对`同一资源同时操作`,导致的问题
(2) 举例 : 比如多个线程访问同一个对象、同一个变量、同一个文件
(3) 结果 : 当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题 (资源强夺)
(4) 解决方案 : 互斥锁 / 同步锁 

代码示例 :  (卖票案例)
多线程开发的复杂度相对较高,在开发时可以按照以下套路编写代码:
  • 首先确保单个线程执行正确
  • 添加线程
 1 #import "ViewController.h"
 2 
 3 @interface ViewController ()
 4 @property(nonatomic, strong) NSThread *thread01; // 售票员01
 5 @property(nonatomic, strong) NSThread *thread02; // 售票员02
 6 @property(nonatomic, strong) NSThread *thread03; // 售票员03
 7 
 8 @property(nonatomic, assign) NSInteger ticketCount; //票的总数
 9 @end
10 
11 @implementation ViewController
12 
13 - (void)viewDidLoad {
14   [super viewDidLoad];
15   // Do any additional setup after loading the view, typically from a nib.
16 
17   self.ticketCount = 10;
18 
19   //创建线程
20   self.thread01 = [[NSThread alloc] initWithTarget:self
21                                           selector:@selector(saleTicket)
22                                             object:nil];
23   self.thread01.name = @"售票员01";
24 
25   self.thread02 = [[NSThread alloc] initWithTarget:self
26                                           selector:@selector(saleTicket)
27                                             object:nil];
28   self.thread02.name = @"售票员02";
29 
30   self.thread03 = [[NSThread alloc] initWithTarget:self
31                                           selector:@selector(saleTicket)
32                                             object:nil];
33   self.thread03.name = @"售票员03";
34 }
35 
36 // 开启线程
37 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
38   [self.thread01 start];
39   [self.thread02 start];
40   [self.thread03 start];
41 }
42 
43 // 卖票
44 - (void)saleTicket {
45 
46   while (1) {
47 
48     @synchronized(self) { //互斥锁(控制器做锁对象)
49       // 先取出总数
50       NSInteger count = self.ticketCount;
51 
52       // 判断还有没有余票
53       if (count > 0) {
54         self.ticketCount = count - 1;
55         NSLog(@"%@卖了一张票,还剩下%zd张", [NSThread currentThread].name,
56               self.ticketCount);
57       } else {
58         NSLog(@"票已经卖完了");
59         break;
60       }
61     }
62   }
63 }
64 
65 - (void)didReceiveMemoryWarning {
66   [super didReceiveMemoryWarning];
67   // Dispose of any resources that can be recreated.
68 }
69 
70 @end
打印结果:

2016-03-17 19:37:27.429 线程安全[3386:472835] 售票员02卖了一张票,还剩下9张
2016-03-17 19:37:27.430 线程安全[3386:472836] 售票员03卖了一张票,还剩下8张
2016-03-17 19:37:27.430 线程安全[3386:472834] 售票员01卖了一张票,还剩下7张
2016-03-17 19:37:27.430 线程安全[3386:472835] 售票员02卖了一张票,还剩下6张
2016-03-17 19:37:27.430 线程安全[3386:472836] 售票员03卖了一张票,还剩下5张
2016-03-17 19:37:27.430 线程安全[3386:472834] 售票员01卖了一张票,还剩下4张
2016-03-17 19:37:27.431 线程安全[3386:472835] 售票员02卖了一张票,还剩下3张
2016-03-17 19:37:27.431 线程安全[3386:472836] 售票员03卖了一张票,还剩下2张
2016-03-17 19:37:27.431 线程安全[3386:472834] 售票员01卖了一张票,还剩下1张
2016-03-17 19:37:27.431 线程安全[3386:472835] 售票员02卖了一张票,还剩下0张
2016-03-17 19:37:27.432 线程安全[3386:472836] 票已经卖完了
2016-03-17 19:37:27.434 线程安全[3386:472834] 票已经卖完了
2016-03-17 19:37:27.434 线程安全[3386:472835] 票已经卖完了

 

4.2-4.2 安全隐患解决 – 互斥锁 / 同步锁

互斥锁使用技术 : 线程同步
概念:多条线程按顺序地执行任务
引申 : 互斥锁,就是使用了线程同步技术
 
互斥锁使用格式 :
1 //锁对象为能够加锁的任意 NSObject 对象
2 //锁对象一定要保证所有的线程都能够访问
3 //如果代码中只有一个地方需要加锁,大多都使用self,这样可以避免单独再创建一个锁对象
4  
5 @synchronized(锁对象) {
6     //需要锁定的代码
7 }
注意:
  • 锁定1份代码只用1把锁,用多把锁是无效的
  • 保证锁内的代码,同一时间,只有一条线程能够执行!
  • 互斥锁的锁定范围,应该尽量小,锁定范围越大,效率越差!
  • 速记 :  [[NSUserDefaults standardUserDefaults] synchronize];
 
互斥锁的使用前提:
多条线程抢夺同一块资源
 
互斥锁的优缺点 :
优点:能有效防止因多线程抢夺资源造成的数据安全问题 , 能保证数据的准确性
缺点:需要消耗大量的CPU资源 , 可能会导致执行速度变慢
 
 
4.2-4.3 原子属性/非原子属性
原子和非原子属性:
(默认) atomic 原子属性 为setter方法加锁 线程安全,需要消耗大量的资源
(推荐) nonatomic 非原子属性 不会为setter方法加锁 非线程安全,适合内存小的移动设备
 
atomic加锁原理:
1 @property (assign, atomic) int age;
2 - (void)setAge:(int)age
3 {
4     @synchronized(self) {
5         _age = age;
6     }
7 }

 

 iOS开发的建议:
  • 所有属性都声明为nonatomic
  • 尽量避免多线程抢夺同一块资源
  • 尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力
 
 4.2-5.0 线程间通讯和常用方法
线程间通信:
概念 : 在1个进程中,线程往往不是孤立存在的,多个线程之间需要经常进行通信
线程间通信的体现 : 
  • 1个线程传递数据给另1个线程
  • 在1个线程中执行完特定任务后,转到另1个线程继续执行任务
 
 
线程间通信常用方法 : 
1 //在主线程执行操作 (从子线程回到主线程)(推荐)
2 - (void)performSelectorOnMainThread:(SEL)aSelector
3                          withObject:(id)arg
4                       waitUntilDone:(BOOL)wait;
5 
6 - (void)performSelector:(SEL)aSelector
7                onThread:(NSThread *)thr
8              withObject:(id)arg
9           waitUntilDone:(BOOL)wait;

另外一种线程之间的通信方式:NSPort(端口)

包括的子类:
(1)NSMessagePort;
(2)NSMachPort;
 
 
4.2-5.1 图片下载示例
 1 #import "ViewController.h"
 2 
 3 @interface ViewController ()
 4 @property(weak, nonatomic) IBOutlet UIImageView *imageView;
 5 @end
 6 
 7 @implementation ViewController
 8 
 9 - (void)viewDidLoad {
10   [super viewDidLoad];
11   // Do any additional setup after loading the view, typically from a nib.
12 }
13 
14 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
15   // 开启子线程(在子线程中调用download方法下载图片)
16   [self performSelectorInBackground:@selector(download) withObject:nil];
17 }
18 
19 #pragma mark - 图片下载
20 - (void)download {
21   // 1.图片的网络路径
22   NSURL *url =
23       [NSURL URLWithString:@"http://www.5068.com/u/faceimg/20140804114111.jpg"];
24 
25   // 2.下载图片并把图片转换为二进制的数据(耗时操作)
26   NSData *data = [NSData dataWithContentsOfURL:url];
27 
28   // 3.生成图片(把数据转换成图片)
29   UIImage *image = [UIImage imageWithData:data];
30 
31   // 4.回到主线程中设置图片
32   // 方法1:
33   [self.imageView
34       performSelector:@selector(setImage:)
35              onThread:[NSThread mainThread]
36            withObject:image
37         waitUntilDone:NO]; //是否等到@selector的方法完成后再往下执行,NO表示否
38 
39   //方法2:
40   [self.imageView performSelectorOnMainThread:@selector(setImage:)
41                                    withObject:image
42                                 waitUntilDone:NO];
43   // 方法3:代码一
44   [self performSelectorOnMainThread:@selector(showImage:)
45                          withObject:image
46                       waitUntilDone:NO];
47 }
48 
49 // 方法3:代码二
50 - (void)showImage:(UIImage *)image {
51   self.imageView.image = image; //设置显示图片
52 }
53 
54 #pragma mark - 测试图片下载时间 方法1:NSDate
55 - (void)download1 {
56   // 1.图片的网络路径
57   NSURL *url =
58       [NSURL URLWithString:@"http://www.5068.com/u/faceimg/20140804114111.jpg"];
59 
60   NSDate *begin = [NSDate date]; //开始时间
61 
62   // 2.根据图片的网络路径去下载图片数据
63   NSData *data = [NSData dataWithContentsOfURL:url];
64 
65   NSDate *end = [NSDate date]; //结束时间
66 
67   NSLog(@"%f", [end timeIntervalSinceDate:begin]); // 时间间隔 = 开始-结束
68 
69   // 3.显示图片
70   self.imageView.image = [UIImage imageWithData:data];
71 }
72 
73 #pragma mark - 测试图片下载时间 方法2:CFTimeInterval
74 - (void)download2 {
75   // 1.图片的网络路径
76   NSURL *url =
77       [NSURL URLWithString:@"http://www.5068.com/u/faceimg/20140804114111.jpg"];
78 
79   CFTimeInterval begin = CFAbsoluteTimeGetCurrent(); // 开始时间
80 
81   // 2.根据图片的网络路径去下载图片数据
82   NSData *data = [NSData dataWithContentsOfURL:url];
83 
84   CFTimeInterval end = CFAbsoluteTimeGetCurrent(); //结束时间
85 
86   NSLog(@"%f", end - begin); // 时间间隔 = 开始-结束
87 
88   // 3.显示图片
89   self.imageView.image = [UIImage imageWithData:data];
90 }
91 
92 - (void)didReceiveMemoryWarning {
93   [super didReceiveMemoryWarning];
94   // Dispose of any resources that can be recreated.
95 }
96 
97 @end

 

 


 

如果你觉得本篇文章对你有所帮助,请点击右下部“推荐”,^_^
 
 
作者:蓝田(Loto)
出处:http://www.cnblogs.com/shorfng/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
 

如有疑问,请发送邮件至 shorfng@126.com 联系我。
 

 

 
posted @ 2016-03-25 10:46  蓝田_Loto  阅读(514)  评论(0编辑  收藏  举报