zoukankan      html  css  js  c++  java
  • 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 联系我。
     

     

     
  • 相关阅读:
    Windows配置深度学习环境详细教程(二):conda工具的使用
    Windows配置深度学习环境详细教程(一):安装Pycharm和Miniconda
    性能基准DevOps之如何提升脚本执行效率
    【Go语言绘图】图片添加文字(二)
    Cesium中用到的图形技术——Computing the horizon occlusion point
    Cesium中用到的图形技术——Horizon Culling
    Unity3D学习笔记3——Unity Shader的初步使用
    C++:异常处理
    mysql数据库备份
    编程小工具
  • 原文地址:https://www.cnblogs.com/shorfng/p/5318557.html
Copyright © 2011-2022 走看看