iOS之多线程的使用

多线程需要掌握的知识点


2.png





线程的注意点

  1. 不要同时开太多线程(1~3条即可,不要超过5条)

  2. 线程概念

  • 主线程:UI线程,显示、刷新UI界面,处理UI控件的事件

  • 子线程:后台线程,异步线程

  1. 不要把耗时操作放在主线程中,容易造成线程阻塞,要放在子线程中执行



多线程的实现方案.png


线程阻塞的情况

#import<pthread.h>

//viewDidLoad
//创建线程
/*
参数1:线程id
参数2:NULL
参数3:线程执行的返回值为void *,参数为void *的函数
参数4:NULL

pthread_create(pthread_t *restrict, const pthread_attr_t *restrict, void *(*)(void *), void *restrict)
*/

NSLog(@"viewDidLoad------%@",[NSThread currentThread]);//打印线程1

pthread_t myRestrict;
pthread_create(myRestrict, NULL, run, NULL);

//run函数
void *run(void *data){
    NSLog(@"run------%@",[NSThread currentThread]);//打印线程2
    return NULL;
}

//touchesBegan
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    for(int i = 0;i < 10000;i++)
    {
        NSLog(@"touchesBegan----%d-----%@",i,[NSThread currentThread]);//在主线程中执行造成线程阻塞
    }
}

子线程执行代码

#import<pthread.h>

//viewDidLoad

//run函数
void *run(void *data){
    for(int i = 0;i < 10000;i++)
    {
        NSLog(@"touchesBegan----%d-----%@",i,[NSThread currentThread]);//在主线程中执行造成线程阻塞
    }
    return NULL;
}

//touchesBegan
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    //创建线程
    pthread_t myRestrict;
    pthread_create(myRestrict, NULL, run, NULL);
}


NSThread

一个NSThread对象就代表一条线程

创建启动线程

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [self createThread1];
    
    [self createThread2];
    
    [self createThread3];
}

/**
 *  创建线程方式1:创建并手动开启线程
 */
-(void)createThread1{
    //创建线程,执行下载方法
    NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(download) object:nil];
    //线程是有名称属性的,可以直接设置,打印时可以直接打印
    thread.name = @"下载线程";
    //判断此线程是否是主线程
    NSLog(@"判断此线程是否是主线程:%d",[thread isMainThread]);
    
    //启动线程(调用self的download方法)
    [thread start];
    //判断当前线程是否是主线程
    NSLog(@"判断当前线程是否是主线程:%d",[NSThread isMainThread]);
}
-(void)download{
    NSLog(@"下载东西---主线程:%@--当前线程%@",[NSThread mainThread],[NSThread currentThread]);
    NSLog(@"判断当前线程是否是主线程:%d",[NSThread isMainThread]);
}

/**
 *  创建线程方式2:创建并自动启动线程
 */
-(void)createThread2{
    //最后一个参数object.用于传值
    [NSThread detachNewThreadSelector:@selector(download:) toTarget:self withObject:@"http://a.jpg"];
}
-(void)download:(NSString *)url{
    NSLog(@"下载东西---主线程:%@--当前线程%@---url:%@",[NSThread mainThread],[NSThread currentThread],url);
    NSLog(@"判断当前线程是否是主线程:%d",[NSThread isMainThread]);
}


/**
 *  创建线程方式3:隐式创建线程
 */
-(void)createThread3{
    //在主线程执行download:方法,相当于:[self download:@"http://c.gif"];
    [self performSelector:@selector(download:) withObject:@"http://c.gif"];
    //创建子线程执行download:方法
    [self performSelectorInBackground:@selector(download:) withObject:@"http://c.gif"];
}

线程的状态


线程的状态.png

注:一旦线程死亡,就不能再次开启任务

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{

#pragma mark ------观察线程状态------
    //新建线程
    NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil];
    //线程就绪
    [thread start];
    
}

-(void)run {
    //开始运行线程
    NSLog(@"-------begin------");
    //睡眠(阻塞)5秒钟
    //[NSThread sleepForTimeInterval:5];
    //从现在开始睡3秒
    [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
    
    
    for (int i = 0; i < 100; i++) {
        NSLog(@"-----%d-----",i);
        //手动结束线程,一旦线程死亡,就不能再次开启任务
        //return;
        [NSThread exit];
    }
    NSLog(@"---------end---------");
    //自动结束线程
}

多线程的安全隐患

资源共享造成多个线程访问同一块资源,引发数据错乱和数据安全问题(存取款、买卖票)

解决方式:互斥锁(同步锁)

注:锁定一份代码只需要一把锁,用多把锁是无效的

@property (strong, nonatomic) NSThread *thread1;
@property (strong, nonatomic) NSThread *thread2;
@property (strong, nonatomic) NSThread *thread3;
/**
 *  剩余票数
 */
@property (assign, nonatomic) int leftTicketCount;

    //viewDidLoad
    self.leftTicketCount = 100;
    
    self.thread1 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.thread1.name = @"1号窗口";
    
    self.thread2 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.thread2.name = @"2号窗口";
    
    self.thread3 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.thread3.name = @"3号窗口";

/**
 *  卖票
 */
-(void)saleTicket{
    while (1) {
        //加锁,()里面是锁对象,可以填任意对象,用self保证只创建一个锁对象
        @synchronized(self) {
            if (self.leftTicketCount > 0) {
                //此时会造成线程阻塞,导致线程之间获取资源相同,导致每个线程卖票时票数相同,所以要给线程加锁解锁
                [NSThread sleepForTimeInterval:0.1];
                self.leftTicketCount--;
                NSLog(@"%@卖了一张票,剩余%d张票",[NSThread currentThread].name,self.leftTicketCount);
            }
            else
            {
                return;//退出循环
            }
        }//解锁
        
    }
}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [self.thread1 start];
    [self.thread2 start];
    [self.thread3 start];
}

互斥锁(同步锁)的优缺点

  1. 优点:能有效防止因多线程抢夺资源造成的数据安全问题,形成线程同步

  2. 缺点:需要消耗大量的CPU资源

原子和非原子属性

OC在定义属性时有nonatomic和atomic两种选择

  1. atomic:原子属性,为setter方法加锁,且属性默认为atomic;线程安全,需要消耗大量的资源

  2. nonatomic:非原子属性,不会为setter方法加锁;非线程安全,适合内存小的移动设备

  3. IOS开发建议

  • 所有属性都声明为nonatomic

  • 尽量避免多线程抢夺同一块资源

  • 尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力


  1. 1个线程传递数据给另1个线程

  2. 在1个线程中执行完特定任务后,转到另1个线程继续执行任务

线程间通信的方法

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [self performSelectorInBackground:@selector(download) withObject:nil];
}
/**
 *  下载图片
 */
-(void)download{
    NSLog(@"download-----------%@",[NSThread currentThread]);

    //1.图片地址
    NSString *urlStr = @"http://www.bz55.com/uploads/allimg/150309/139-150309101F2.jpg";
    //2.根据地址下载图片的二进制数据
    NSLog(@"---begin---");
    NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:urlStr]];
    NSLog(@"---end---");
    //3.设置图片
    UIImage *image = [UIImage imageWithData:data];
    
    //4.回到主线程,刷新UI界面(为了线程安全)
    //[self performSelectorOnMainThread:@selector(downloadFinished:) withObject:image waitUntilDone:YES];
    //[self performSelector:@selector(download) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
    //最后一个参数用于设置是否等待子线程完成
    [self.imgView performSelector:@selector(setImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
}

//-(void)downloadFinished:(UIImage *)image{
//    self.imgView.image = image;
//    NSLog(@"downloadFinished-----------%@",[NSThread currentThread]);
//}

GCD(Grand Central Dispatch--中枢调度器

优势

  1. GCD是Apple为多核的并行运算提出的解决方案

  2. GCD会自动利用更多的CPU内核(双核、四核)

  3. GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)

  4. 程序员只要告诉GCD想要执行什么任务,不需要编写任何线程管理代码

概念

  1. 任务:执行什么操作

  2. 队列:用来存放任务

步骤

  1. 定制任务:确定想做的操作

  2. 将任务添加到队列中

  • GCD自动将队列中的任务取出,放到对应的线程中执行

  • 任务的取出遵循队列的FIFO原则:先进先出,后进后出

队列的类型

  1. 并发队列(Concurrent Dispatch Queue)

  • 可以让多个任务并发执行

  • 并发功能只有在异步函数下才有效

  1. 串行队列(Serial Dispatch Queue)
    让任务一个接一个的执行

  1. dispatch_sync:同步,不具备开启线程的能力

  2. dispatch_async:异步,具备开启线程的能力

各种队列的执行效果


各种队列的执行效果.png

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    [self asyncGlobalQueue];
    
    [self asyncSerialQueue];
    
    [self asyncMainQueue];
    
    [self syncGlobalQueue];
    
    [self syncSerialQueue];
    
    }

#pragma mark --------常用----------

/**
 *  async--并发队列(最常用)
 *  会不会创建线程:会,一般同时创建多条线程
 *  任务的执行方式是并发的
 */
-(void)asyncGlobalQueue{
    //获得全局的并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    //将任务添加到全局队列中异步执行
    dispatch_async(queue, ^{
        NSLog(@"异步加载1-------%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"异步加载2-------%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"异步加载3-------%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"异步加载4-------%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"异步加载5-------%@",[NSThread currentThread]);
    });
}


/**
 *  async--串行队列(有时候用)
 *  会不会创建线程:会,一般只开一条线程
 *  任务的执行方式是串行的
 */
-(void)asyncSerialQueue{
    //1.创建一个串行队列
    dispatch_queue_t queue = dispatch_queue_create("asyncSerialQueue", NULL);
    //2.将任务添加到串行队列中异步执行
    dispatch_async(queue, ^{
        NSLog(@"异步加载1-------%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"异步加载2-------%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"异步加载3-------%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"异步加载4-------%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"异步加载5-------%@",[NSThread currentThread]);
    });
    
    //3.非ARC情况下,需要释放创建的队列;此时为ARC,不能用release
    //dispatch_release(queue);

}

/**
 *  async--主队列(很常用,常用于线程之间传值)
 */
-(void)asyncMainQueue{
    //1.主队列(添加到主队列中的任务都会自动放到主线程中执行)
    dispatch_queue_t queue = dispatch_get_main_queue();
    //2.添加任务到主线程中异步执行
    dispatch_async(queue, ^{
        NSLog(@"异步加载1-------%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"异步加载2-------%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"异步加载3-------%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"异步加载4-------%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"异步加载5-------%@",[NSThread currentThread]);
    });
}

/**
 *  sync--主队列(不能用)
 */
-(void)syncMainQueue{
    //1.主队列(添加到主队列中的任务都会自动放到主线程中执行)
    dispatch_queue_t queue = dispatch_get_main_queue();
    //2.添加任务到主线程中同步执行
    dispatch_sync(queue, ^{
        NSLog(@"异步加载1-------%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"异步加载2-------%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"异步加载3-------%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"异步加载4-------%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"异步加载5-------%@",[NSThread currentThread]);
    });
}



#pragma mark --------不常用----------

/**
 *  sync--并发队列
 *  会不会创建线程:不会
 *  任务的执行方式:串行执行(一个任务执行完之后再执行下一任务)
 *  并发队列失去了并发的功能。因为不存在多线程
 */
-(void)syncGlobalQueue{
    //获得全局的并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //将任务添加到全局队列中同步执行
    dispatch_sync(queue, ^{
        NSLog(@"异步加载1-------%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"异步加载2-------%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"异步加载3-------%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"异步加载4-------%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"异步加载5-------%@",[NSThread currentThread]);
    });
}

/**
 *  sync--串行队列
 *  会不会创建线程:不会
 *  任务的执行方式是串行的
 */
-(void)syncSerialQueue{
    //1.创建一个串行队列
    dispatch_queue_t queue = dispatch_queue_create("syncSerialQueue", NULL);
    //2.将任务添加到串行队列中异步执行
    dispatch_sync(queue, ^{
        NSLog(@"异步加载1-------%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"异步加载2-------%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"异步加载3-------%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"异步加载4-------%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"异步加载5-------%@",[NSThread currentThread]);
    });
    
}

框架之间数据类型转换

Foundation:OC框架
Core Foundation:C语言框架
Foundation和Core Foundation框架的数据类型可以互相转换的

编号 Foundation Core Foundation
1 NSString CFStringRef
2 NSArray CFArrayRef
3 NSDictionary CFDictionaryRef
4 NSNumber CFNumberRef

强制转换语法:

NSString *str = @"123"; //Foundation框架
//不同框架数据类型转换需要进行强制转换:桥接__bridge
CFStringRef str2 = (__bridge CFStringRef)(str) ;  //Core Foundation框架
NSString *str3 = (__bridge NSString *)(str2);
NSLog(@"str2 = %@",(__bridge id)(str2));
NSLog(@"str3 = %@",str3);

Core Foundation中手动创建的数据类型,都需要手动释放
总结:

  1. 凡是函数名中带有create\copy\new\retain等字眼,都应该在不需要使用这个数据时进行release

  2. GCD的数据类型在ARC环境下不需要release

  3. CF(Core Foundation)的数据类型在ARC|MRC环境下,均需要Release

    CFArrayRef array = CFArrayCreate(NULL, NULL, 10, NULL);    
    //释放
    CFRelease(array);
    
    CGPathRef path = CGPathCreateMutable();
    CGPathRelease(path);
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    NSLog(@"-----Began------");
    
    [self delay1];
    
    [self delay2];
    
    [self delay3];
    
    NSLog(@"-----end------");
    
}

/**
 *  方法一:sleep,不建议使用,因为会卡掉线程
 */
-(void)delay1{
    [NSThread sleepForTimeInterval:3];
    //延迟3秒执行
    NSLog(@"-------delay1--------");
}

/**
 *  方法二:延迟执行所要进行的方法,较常用
 *  一旦定制好延迟任务后,不会卡当前线程
 */
-(void)delay2{
    //写在哪个线程里就在那个线程执行
    //3秒后执行事件download: 虽然是在主线程里执行的,但不会卡住主线程
    [self performSelector:@selector(download:) withObject:@"haha" afterDelay:3];
}
-(void)download:(NSString *)str{
    NSLog(@"-----download:---%@----%@--",str,[NSThread currentThread]);
}

/**
 *  方法三:GCD,
 */
-(void)delay3{
    //可以直接设置是在主线程中执行还是在子线程中执行
    //从当前时间起延迟3秒  : 虽然是在主线程里执行的,但不会卡住主线程
    
    //3秒后回到主线程,执行block中的代码
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"----- GCD delay main ------%@--",[NSThread currentThread]);
    });
    
    //3秒后自动开启新线程,执行block中的代码
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), queue, ^{
        NSLog(@"----- GCD delay global ------%@--",[NSThread currentThread]);
    });
}

一次性代码(单例)

使用dispatch_once函数能保证某段代码在程序运行过程中只被执行1次
可以用于单例

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //这里面默认是线程安全的
        NSLog(@"----下载图片----");
    });

单例模式

可以保证在程序运行期间,一个类只有一个实例,而且该实例供外界访问

  1. 懒汉式:第一次用到单例对象时,再创建

  • ARC

  • MRC

  1. 饿汉式:一进入程序就创建一个单例对象

static的作用

  1. 修饰全局变量:全局变量的作用域仅限于当前文件内部,此时外部文件extern该变量就引用不到了,防止其他文件恶意篡改变量值

  2. 修饰局部变量:保证局部变量永远只初始化一次,在程序运行过程中,永远只有一份内存。但是a还是只能用在局部变量作用域范围内

队列组(合并图片——水印)

将任务存放于队列中,将队列存放与队列组中,而队列组的特性是保证执行完组里面的所有任务后,再执行notify函数里面的block,故可以保证子线程执行完毕后执行主线程

/**
 *  方法二:队列组
 */
-(void)test2{
    //1.队列组
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //2.下载图片一
    __block UIImage *img1 = nil;
    dispatch_group_async(group, queue, ^{
        //下载第一张图片:当图片地址不标准时会出错,例如网址中有‘,’等现象
        NSData *dataBigImg = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://imgsrc.baidu.com/forum/pic/item/e3f7237f9e2f07084d0553f7e924b899a801f2e2.jpg"]];
        img1 = [UIImage imageWithData:dataBigImg];
    });
    //3.下载图片二
    __block UIImage *img2 = nil;
    dispatch_group_async(group, queue, ^{
        //下载第二张图片
        NSData *dataSmallImg = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://img1.bdstatic.com/static/home/widget/search_box_home/logo/home_white_logo_0ddf152.png"]];
        img2 = [UIImage imageWithData:dataSmallImg];
    });
    //4.合并图片(保证执行完组里面的所有任务后,再执行notify函数里面的block)
    dispatch_group_notify(group, queue, ^{
        UIGraphicsBeginImageContextWithOptions(img1.size, NO, 0.0);
        [img1 drawInRect:CGRectMake(0, 0, img1.size.width, img1.size.width)];
        [img2 drawInRect:CGRectMake(0, 0, img2.size.width, img2.size.height)];
        UIImage *fullImg = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        
        //5.跳转主线程,加载图片
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imgView.image = fullImg;
        });
    });
    //ARC环境下就不需要release了
    //dispatch_release(group);
}

延迟加载的三种方法

Core Foundation框架注意事项

两个函数

线程间通信

pthread

猜你喜欢

转载自blog.csdn.net/wks_lovewei/article/details/52595034
今日推荐