美文网首页@IT·互联网
多线程学习笔记

多线程学习笔记

作者: 宙斯YY | 来源:发表于2017-06-09 14:55 被阅读56次

一.线程基础概念

1.线程生命周期
  • 线程生命周期 新建-就绪-运行-死亡,运行中可以进入阻塞状态
  • 线程执行完任务会自动销毁(死亡),当然可以手动让线程强制退出
2.线程安全

解决卖票问题:

  • @synchronize包裹代码块或者atomic修饰公用属性
  • @synchronized(self)包裹的代码块只允许一个线程执行,当线程执行到synchronized,使用self加锁,其他线程不能进入。执行完之后从排队的线程中唤醒第一个线程执行。
- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.tickets=10000;
    NSThread * thread1=[[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
    thread1.name=@"t1";
    NSThread * thread2=[[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
    thread2.name=@"t2";
    NSThread * thread3=[[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
    thread3.name=@"t3";

    [thread1 start];
    [thread2 start];
    [thread3 start];
    
}

-(void)saleTicket
{
   
    while (YES) {
         @synchronized(self){
             if(self.tickets>0)
             {
                 self.tickets--;
                 NSLog(@"%@:%d",[NSThread currentThread].name,self.tickets);
             }else
             {
                 NSLog(@"卖完了 %@:%d",[NSThread currentThread].name,self.tickets);
                  break;
             }
             
         }
    }
}

GCD和NSOperation是IOS最常用的两种多线程方式,这里跟大家分享一下使用中的心得。

二.GCD学习笔记

1.dispatch_sync—同步操作

dispatch_queue_t concurrentQueue =dispatch_queue_create("my.concurrent.queue”,DISPATCH_QUEUE_CONCURRENT);

NSLog(@"1");

dispatch_sync(concurrentQueue, ^(){

NSLog(@"2");

[NSThread sleepForTimeInterval:10];

NSLog(@"3");

});

NSLog(@"4”);

输出信息:12(10s后)34
注:sync包裹的代码块阻塞住当前线程执行代码,包裹的代码块本身依然是顺序执行的

2.dispatch_async—异步操作

dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);

NSLog(@"1");

dispatch_async(concurrentQueue, ^(){

NSLog(@"2");

[NSThread sleepForTimeInterval:5];

NSLog(@"3");

});

NSLog(@"4”);

输出 1 4 2 (5s后)3
注:async不会阻塞当前线程的代码执行,包裹的代码块的本身依然是顺序执行的

特别注意:不能理解为同步是在主线程队列去执行的,而异步是开一个新的线程队列去执行代码!!同步方法调用就是在占用当前线程(可以是主线程也可以是子线程)资源去执行代码,执行完同步方法才可以执行后面代码;异步方法就是在当前线程(可以是主线程也可以是子线程)基础上开辟新的执行分支,让异步方法之后的代码不被异步方法代码阻塞掉。

3.GCD处理费时操作的方式

常见问题分析思路:主观感觉就是与UI处理相关的操作,比如说点击滑动如果出现卡顿,可能是主线程执行耗时操作导致。

dispatch_async(dispatch_get_global_queue(0, 0), ^{

//处理耗时操作的代码块...

//通知主线程刷新

dispatch_async(dispatch_get_main_queue(), ^{

//回调或者说是通知主线程刷新,

});

});

4.队列的概念(GCD除了线程同步异步还有队列的概念)

同步异步是线程概念,串行并行是队列概念。

使用GCD的时候,我们会把需要处理的任务放到Block里面,然后讲任务追加到相应的队列里面,这个队列,叫Dispatch Queue。然而,存在于两种Dispatch Queue,一种等待上一个任务执行完毕再执行下一个任务的是串行队列(如果上面任务没有执行完毕,下面的任务就不能执行,造成死锁的现象),一种是不需要等待上一个任务执行完毕,就能执行下一个的并行队列。GCD为我们提供两个队列,

dispatch_get_global_queue//全局并行队列

dispatch_get_main_queue//主串行队列

dispatch_queue_t concurrentQueue =dispatch_queue_create("my.concurrent.queue”,DISPATCH_QUEUE_SERIAL);//—串行队列

dispatch_queue_t concurrentQueue =dispatch_queue_create("my.concurrent.queue”,DISPATCH_QUEUE_CONCURRENT);//并行队列

那么,这两种队列和全局并行对列以及主线程串行队列的区别在哪呢,举例说明。

a.定义同步异步两种方法,循环打印3次当前线程
-(void)async:(dispatch_queue_t)queue
{
    
        
        NSLog(@"begin %@",[NSThread currentThread]);
        
        for (int i=0; i<3; i++) {
            
            dispatch_async(queue, ^{
                
                NSLog(@"async:%@",[NSThread currentThread]);
                
            });
        }
        
        NSLog(@"end %@",[NSThread currentThread]);
    
    

}

-(void)sync:(dispatch_queue_t)queue
{
    
        
    NSLog(@"begin %@",[NSThread currentThread]);
    
    for (int i=0; i<3; i++) {
        
        dispatch_sync(queue, ^{
        
            NSLog(@"sync:1 %@",[NSThread currentThread]);
        
        });
    }
    
    NSLog(@"end %@",[NSThread currentThread]);
  

}
b.创建四种队列,在主线程中分别调用同步异步方法,查看打印结果。
//1.系统主线程串行队列
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    //2.系统全局并行队列
    dispatch_queue_t globelQueue = dispatch_get_global_queue(0, 0);
    //3.自定义串行队列
    dispatch_queue_t serialQueue = dispatch_queue_create("SERIAL", DISPATCH_QUEUE_SERIAL);
    //4.自定义并行队列
    dispatch_queue_t concurrentQueue = dispatch_queue_create("CONCURRENT", DISPATCH_QUEUE_CONCURRENT);
    
    /*在主线程调用*/
    //[self sync:mainQueue];   //begin 1
    
    //[self sync:globelQueue];    //begin 1 sync 1 sync 1 sync 1 end 1
    
    //[self sync:serialQueue];  //begin 1 sync 1 sync 1 sync 1 end 1
    
    //[self sync:concurrentQueue];  //begin 1 sync 1 sync 1 sync 1 end 1

    //[self async:mainQueue];  //begin 1 end 1 async 1 async 1 async 1
    
    //[self async:globelQueue];  //begin 1 end 1 async 3  async 5 async 4
    
    //[self async:serialQueue];   //begin 1 end 1 async 4 async 4 async 4
    
    //[self async:concurrentQueue]; //begin 1 end 1 async 6 async 4 async 5

此时的结论是(此时):
1.主线程中调用同步方法,不开辟新线程。如果调用同步主线程,则会形成死锁(第一种情况)。
2.主线程中调用异步方法,除调用异步主线程外,都开辟新线程。

加工一下同步异步方法,让该同步异步方法在非主线程队列中执行。

-(void)async2:(dispatch_queue_t)queue
{
    
    dispatch_async(queue, ^{
        
        NSLog(@"begin %@",[NSThread currentThread]);
        
        for (int i=0; i<3; i++) {
            
            dispatch_async(queue, ^{
                
                NSLog(@"async:%@",[NSThread currentThread]);
                
            });
        }
        
        NSLog(@"end %@",[NSThread currentThread]);
    });
    
    
}

-(void)sync2:(dispatch_queue_t)queue
{
    dispatch_async(queue, ^{
        
        NSLog(@"begin %@",[NSThread currentThread]);
        
        for (int i=0; i<3; i++) {
            
            dispatch_sync(queue, ^{
                
                NSLog(@"sync:1 %@",[NSThread currentThread]);
                
            });
        }
        
        NSLog(@"end %@",[NSThread currentThread]);
    });
    
}

运行的结果是:

/*在子线程中调用*/
    
    //[self sync2:mainQueue];   //begin 1
    
    //[self sync2:globelQueue];    //begin 3 sync 3 sync 3 sync 3 end 3
    
    //[self sync2:serialQueue];  //begin 4
    
    //[self sync2:concurrentQueue];  //begin 3 sync 3 sync 3 sync 3 end 3
    
    //[self async2:mainQueue];  //begin 1 end 1 async 1 async 1 async 1
    
    //[self async2:globelQueue];  //begin 3 end 3 async 4  async 5 async 6
    
    //[self async2:serialQueue];   //begin 4 end 4 async 4 async 4 async 4
    
    //[self async2:concurrentQueue]; //begin 3 end 3 async 6 async 4 async 5

此时结论是:
1.子线程中调用同步方法,不开辟新线程。如果调用同步当前线程队列,则会形成死锁(第一种情况)。
2.子线程中调用异步方法,除调用异步当前队列外,都开辟新线程。

最终结论是:

1.在当前线程下(主线程或子线程),sync同步当前线程所在队列,如果是该队列是串行队列,一定会死锁,如果是并行队列,则不开辟新线程;async异步当前线程所在队列,如果是该队列是串行队列,不开辟新线程,如果是并行队列,则开辟新线程;async异步非当前线程所在队列,如果是该队列是串行队列,开辟一条新线程,如果是并行队列,则可能开辟多条新线程。

  • 所以分析这类型问题的步骤是:
A.先看当前队列是什么队列;

比如主线程队列,串行子线程队列或者并行子线程队列。

B.再看是同步还是异步方式;

不管在什么队列执行同步,都不会开辟新线程;异步可能会开辟新线程。

C.再看同步异步的任务是在哪个队列;

如果异步在并行队列,可能由多条线程执行任务;异步在新的串行队列,只开辟一条新线程;异步到当前串行线程,不开辟新线程,但不会产生该线程阻塞;同步到当前串行线程,不开辟新线程,但发生死锁;同步到当前并行线程,不开辟新线程,但会产生线程阻塞。


WX20180611-152757.png

2.可以理解为全局并行队列是自定义并行队列的实例,主线程队列是自定义串行队列的实例,之所以在例1中展示不同是因为当前运行程序的线程就是主线程队列而已。
补充:全局队列和自定义并行队列的区别:
全局队列是系统为我们创建好的并行队列;
使用dispatch_barrier的地方只能使用自定义并行队列。

3.串行和并行是指存放任务的队列,区别在于,如果每个队列里面有多个任务,开辟新线程去执行任务时,并行开辟多个线程,串行只开辟一个线程。

理解了这点,那么,我们看几个多线程的例子就很容易理解输出结果了。
例子1

NSLog(@"1"); // 任务1
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2"); // 任务2
});
NSLog(@"3"); // 任务3

结果,控制台输出:

1

例子2

NSLog(@"1"); // 任务1
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(@"2"); // 任务2
});
NSLog(@"3"); // 任务3

结果,控制台输出

1
2
3

例子3

dispatch_queue_t queue = dispatch_queue_create("com.demo.serialQueue", DISPATCH_QUEUE_SERIAL);
NSLog(@"1"); // 任务1
dispatch_async(queue, ^{
NSLog(@"2"); // 任务2
dispatch_sync(queue, ^{
NSLog(@"3"); // 任务3
});
NSLog(@"4"); // 任务4
});
NSLog(@"5"); // 任务5

结果,控制台输出:

1
5
2
// 5和2的顺序不一定

例子4

NSLog(@"1"); // 任务1
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"2"); // 任务2
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"3"); // 任务3
});
NSLog(@"4"); // 任务4
});
NSLog(@"5"); // 任务5

结果,控制台输出:

1
2
5
3
4
// 5和2的顺序不一定
5.dispatch_after 延后执行

dispatch_after 只是延时提交到任务队列,不是提交后延时队列执行。

6.dispatch_apply

快速迭代,可以进行相互不关联耗时的多个相同操作

//创建多个线程(包含主线程)进行遍历,不能使用dispatch_get_main_queue,否则死锁
 dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t i) {
        NSLog(@"i:%zd %@",i,[NSThread currentThread]);
    });
7.dispatch_group

队列组,比队列强大的是可以监听队列的任务状态,比如下载两张图合成一张图片,必须监听任务完成状态。

dispatch_group_t group=dispatch_group_create();
    dispatch_queue_t queue1=dispatch_queue_create("a", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t queue2=dispatch_queue_create("b", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_async(group, queue1, ^{
        NSLog(@"下载1");
    });
    dispatch_group_async(group, queue1, ^{
        NSLog(@"下载2");
    });
    dispatch_group_async(group, queue2, ^{
        NSLog(@"下载3");
    });
    dispatch_group_async(group, queue2, ^{
        NSLog(@"下载4");
    });
    //dispatch_get_main_queue表示执行完毕后在主线程执行代码
    dispatch_group_notify(group,dispatch_get_main_queue(), ^{
        NSLog(@"完成队列任务:合成图片");
    });
8.单例模式

注意点:
1.static 修饰_instance,防止对象释放
2.使用dispatch_once重写allocWithZone,防止多线程创建不同对象
3.重写copyWithZone,mutableCopyWithZone,保证copy的时候也是同一个对象
4.写一个类方法获得实例

static JSXCacheTool *_instance;
+(instancetype)allocWithZone:(struct _NSZone *)zone
{
    //    @synchronized (self) {
    //        // 为了防止多线程同时访问对象,造成多次分配内存空间,所以要加上线程锁
    //        if (_instance == nil) {
    //            _instance = [super allocWithZone:zone];
    //        }
    //        return _instance;
    //    }
    // 也可以使用一次性代码
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if (_instance == nil) {
            _instance = [super allocWithZone:zone];
        }
    });
    return _instance;
}
// 为了使实例易于外界访问 我们一般提供一个类方法
// 类方法命名规范 share类名|default类名|类名
+(instancetype)shareCacheTool
{
    //return _instance;
    // 最好用self 用Tools他的子类调用时会出现错误
    return [[self alloc]init];
}
// 为了严谨,也要重写copyWithZone 和 mutableCopyWithZone
-(id)copyWithZone:(NSZone *)zone
{
    return _instance;
}
-(id)mutableCopyWithZone:(NSZone *)zone
{
    return _instance;
}

三.NSOperation学习笔记

NSOperation是基于GCD的延伸。

NSOperation做GCD的事情:

//主线程队列 
NSOperationQueue * queue = [NSOperationQueue mainQueue]

//自定义并行队列 
NSOperationQueue * queue = [[NSOperationQueue alloc]init];

//自定义串行队列 
NSOperationQueue * queue = [[NSOperationQueue alloc]init];
queue.maxConcurrentOperationCount=1;

默认添加到队列的操作都会开辟新线程。

创建NSOperation的方法可以继承NSOperation类,重新main方法,也可以使用NSBlockOperation,NSInvocationOperation。

NSOperation做GCD以外的事情:

1.给NSOperation添加依赖,让任务可以按照自己想要的优先级顺序执行。

2.设置NSOperationQueue最大并发数。

3.控制NSOperation的状态(暂停,恢复,取消)

相关文章

网友评论

    本文标题:多线程学习笔记

    本文链接:https://www.haomeiwen.com/subject/fjqfqxtx.html