多线程

2019-06-09  本文已影响0人  小石头呢

一.多线程的基本概念

二.多线程的生命周期

线程的生命周期是:新建 - 就绪 - 运行 - 阻塞 - 死亡

三.多线程的四种解决方案

四.phread

1.创建pthread_create与释放pthread_detach线程

char *str = "...";

pthread_t thread;
    
//1.创建一个线程
pthread_create(&thread, NULL, task, str);
        
//2.线程执行完毕 自动释放内存资源
pthread_detach(thread);

void *task(void *str){

}

创建方法pthread_create(<#pthread_t _Nullable restrict _Nonnull#>, <#const pthread_attr_t restrict _Nullable#>, <#void _Nullable ( _Nonnull)(void * _Nullable)#>, <#void *restrict _Nullable#>)可以看出,第一个参数是需要一个Pthread 对象指针,第三个是需要一个C语言函数方法(就当于OC中绑定的执行方法),至于第二个和第四个参数,暂时没有什么用,可以直接传入NULL

2.创建pthread_mutex_init与销毁pthread_mutex_destroy信号量(互斥锁)

pthread_mutex_t mutex;

//创建信号量(互斥锁)
pthread_mutex_init(&mutex, NULL);

 //销毁
pthread_mutex_destroy(&mutex);

4.上锁pthread_mutex_lock与解锁pthread_mutex_unlock

//上锁
pthread_mutex_lock(&mutex);

//锁住的代码

//解锁
pthread_mutex_unlock(&mutex);

5.利用[NSThread currentThread]打印当前线程

6.代码使用

#import "ViewController.h"
#import <pthread.h>

/**信号量*/
static pthread_mutex_t mutex;

static int total = 20;

@interface ViewController ()

@end

@implementation ViewController

-(void)dealloc{
    //销毁
    pthread_mutex_destroy(&mutex);
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //创建信号量(互斥锁)
    pthread_mutex_init(&mutex, NULL);
    
    //循环创建
    for (int i = 0; i < 20; i++) {
        
        NSString *threadID = [NSString stringWithFormat:@"%d",i+1];
        
        char *str = (char *)threadID.UTF8String;
        
        pthread_t thread;
    
        //1.创建一个线程
        pthread_create(&thread, NULL, task, str);
        
        //2.线程执行完毕 自动释放内存资源
        pthread_detach(thread);
    }
    
}

void *task(void *str){
    
    //上锁
    pthread_mutex_lock(&mutex);
    
    
    if (total > 0) {
        
        NSLog(@"线程%s 购票成功 总共:%d张 剩余:%d张",str,total,total-1);
        
        total -= 1;
    }
    
    //解锁
    pthread_mutex_unlock(&mutex);
    
    return NULL;
}

@end

五.NSThread

1.四种创建方式

//1.alloc init
NSThread *thread = [[NSThread alloc] 
             initWithTarget:self selector:@selector(test:) object:@"str"];

thread.name = @"子线程1";

[thread start];
//2.block
thread = [[NSThread alloc] initWithBlock:^{

      NSLog(@"%@",[NSThread currentThread]);
}];
    
thread.name = @"子线程2";

[thread start];
//3.通过detach方式创建好之后自动启动
[NSThread detachNewThreadWithBlock:^{

     NSLog(@"%@",[NSThread currentThread]);
}];
//4.隐式创建,直接启动
[self performSelectorInBackground:@selector(test:) withObject:@"perform"];
-(void)test:(NSString *)str{
    
    NSLog(@"%@",str);
    NSLog(@"%@",[NSThread currentThread]);
}

2.阻塞休眠

//休眠多久
[NSThread sleepForTimeInterval:2];

//休眠到指定时间
[NSThread sleepUntilDate:[NSDate date]];

3.类方法补充

//退出线程
[NSThread exit];
//判断当前线程是否为主线程
[NSThread isMainThread];
//判断当前线程是否是多线程
[NSThread isMultiThreaded];
//主线程的对象
NSThread *mainThread = [NSThread mainThread];

4.NSThread的一些属性

//线程是否在执行
thread.isExecuting;
//线程是否被取消
thread.isCancelled;
//线程是否完成
thread.isFinished;
//是否是主线程
thread.isMainThread;
//线程的优先级,取值范围0.0到1.0,默认优先级0.5,1.0表示最高优先级,优先级高,CPU调度的频率高
 thread.threadPriority;

5.互斥锁

//方法1
@synchronized(self){
                
   //被锁住的代码             
}

//方法2
NSLock *lock;  //创建锁

[self.lock lock];  //加锁
  
//被锁住的代码
          
[self.lock unlock];  //解锁

6.线程间通信

在一个进程中,线程往往不是孤立存在,多个线程需要经常进行通信,一般表现为一个线程传递数据给另外一个线程,或者在一个线程中执行完一个特定任务后,转到另一个线程继续执行任务

//方法
- (void)performSelectorOnMainThread:(SEL)aSelector 
                            withObject:(id)arg waitUntilDone:(BOOL)wait;

 - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr 
                            withObject:(id)arg waitUntilDone:(BOOL)wait;

7.代码例子

#import "ViewController.h"

static int total = 20;

@interface ViewController ()

@property (weak, nonatomic) IBOutlet UIImageView *imageView;

/**锁*/
@property (nonatomic,strong) NSLock *lock;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //创建锁
    self.lock = [[NSLock alloc] init];
    
    //1.alloc init
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(test:) object:@"str"];

    thread.name = @"子线程1";

    [thread start];
    
    //2.block
    thread = [[NSThread alloc] initWithBlock:^{

        NSLog(@"%@",[NSThread currentThread]);
    }];
    
    thread.name = @"子线程2";

    [thread start];
    
    //3.detach 自动开启线程
    [NSThread detachNewThreadWithBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
        
        NSURL *url = [NSURL URLWithString:@"https://ss0.bdstatic.com/94oJfD_bAAcT8t7mm9GUKT-xh_/timg?image&quality=100&size=b4000_4000&sec=1558750668&di=eb9d96f21b34ff93dafdfbdb7338677f&src=http://img3.duitang.com/uploads/item/201503/07/20150307203046_nRfZw.thumb.700_0.jpeg"];
        
        NSData *data = [NSData dataWithContentsOfURL:url];
        
        UIImage *img = [UIImage imageWithData:data];
        
        [self performSelectorOnMainThread:@selector(updateUI:) withObject:img waitUntilDone:YES];
        
    }];
    
    //4.performSelector
    [self performSelectorInBackground:@selector(test:) withObject:@"perform"];
    
    //安全问题
    for (int i = 0; i < 20; i++) {
        
        //创建线程
        [NSThread detachNewThreadWithBlock:^{
            NSObject *obj = [NSObject new];
            @synchronized(obj){
                
                NSLog(@"%d线程 总共:%d张 剩余:%d张",i,total,total-1);
                total -= 1;
            }
            
            
            //加锁
            [self.lock lock];
            
            NSLog(@"---%d线程 总共:%d张 剩余:%d张",i,total,total-1);
            total -= 1;
            
            //解锁
            [self.lock unlock];
        }];
    }
    
}

-(void)test:(NSString *)str{
    
    NSLog(@"%@",str);
    NSLog(@"%@",[NSThread currentThread]);
}

-(void)updateUI:(UIImage *)img{
    
    self.imageView.image = img;
}

@end

六.GCD

1.使用GCD的好处

2.队列和任务

1.队列:指执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,采用 FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。

GCD 中有两种队列:串行队列和并发队列。两者都符合 FIFO(先进先出)的原则。两者的主要区别是:执行顺序不同,以及开启线程数不同。

串行队列:
每次只有一个任务被执行。让任务一个接着一个地执行。
只开启一个线程,一个任务执行完毕后,再执行下一个任务。

并发队列:
可以让多个任务并发(同时)执行。
可以开启多个线程,并且同时执行任务。
并发队列的并发功能只有在异步函数下才有效

2.任务:线程中执行的操作,执行任务有两种方式:同步执行(sync)和异步执行(async)。两者的主要区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力。

1.同步执行(sync):
同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之
后再继续执行。
只能在当前线程中执行任务,不具备开启新线程的能力。

2.异步执行(async):
异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。
可以在新的线程中执行任务,具备开启新线程的能力。
异步虽然具有开启新线程的能力,但是并不一定开启新线程。

3.GCD的使用

4.队列的创建

1.串、并行队列的创建

// 串行队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);

// 并发队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);

2.特殊的串行队列-主队列

//所有放在主队列中的任务,都会放到主线程中执行

// 主队列的获取方法
dispatch_queue_t queue = dispatch_get_main_queue();

3.全局并发队列

//第一个参数表示队列优先级,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT。第二个参数暂时没用,用0即可。

// 全局并发队列的获取方法
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

5.任务的创建

1.同步、异步任务的创建

// 同步执行任务创建方法
dispatch_sync(queue, ^{
    // 这里放同步执行任务代码
});

// 异步执行任务创建方法
dispatch_async(queue, ^{
    // 这里放异步执行任务代码
});

2.队列与任务的搭配

//1.创建队列

//2.创建任务

//串行队列 所有的任务都是按照顺序执行
dispatch_queue_t queue1 = dispatch_queue_create("identifier1", DISPATCH_QUEUE_SERIAL);

//创建任务 同步 异步

//串行队列添加同步任务
//没有开辟新的线程
dispatch_sync(queue1, ^{
    NSLog(@"串-同步:%@",[NSThread currentThread]);
});

//串行队列添加异步任务
//先添加的任务先执行,在同一个线程
dispatch_async(queue1, ^{
    NSLog(@"串-异步:%@",[NSThread currentThread]);
});

dispatch_async(queue1, ^{
    NSLog(@"串-异步:%@",[NSThread currentThread]);
});

dispatch_async(queue1, ^{
    NSLog(@"串-异步:%@",[NSThread currentThread]);
});

//并行队列
dispatch_queue_t queue2 = dispatch_queue_create("identifier1", DISPATCH_QUEUE_CONCURRENT);

//创建任务 同步 异步

//并行队列添加同步任务
//没有开辟新的线程
dispatch_sync(queue2, ^{
    NSLog(@"并-同步:%@",[NSThread currentThread]);
});

//并行队列添加异步任务
//并发执行,每个任务都开辟一个线程
dispatch_async(queue2, ^{
    NSLog(@"并-异步:%@",[NSThread currentThread]);
});

dispatch_async(queue2, ^{
    NSLog(@"并-异步:%@",[NSThread currentThread]);
});

//在主队列
dispatch_async(dispatch_get_main_queue(), ^{
    
    NSLog(@"主-异步:%@",[NSThread currentThread]);
});

主队列加同步任务的情况出现死锁的解释请看后续的死锁

6.GCD 线程间的通信

/**
 * 线程间通信
 */
- (void)communication {
    // 获取全局并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
    // 获取主队列
    dispatch_queue_t mainQueue = dispatch_get_main_queue(); 
    
    dispatch_async(queue, ^{
        // 异步追加任务
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        }
        
        // 回到主线程
        dispatch_async(mainQueue, ^{
            // 追加在主线程中执行的任务
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        });
    });
}

7. GCD 的其他方法

1.栅栏方法:dispatch_barrier_async

2.延时执行方法:dispatch_after

3.一次性代码(只执行一次):dispatch_once

4.快速迭代方法:dispatch_apply

5.队列组:dispatch_group

6.信号量:dispatch_semaphore

七.NSOperation

八.线程安全问题

当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题。就好比几个人在同一时修改同一个表格,造成数据的错乱

解决多线程安全问题的方法

1.互斥锁(同步锁)

@synchronized(锁对象) {

    // 需要锁定的代码
}

如果代码中只有一个地方需要加锁,大多都使用self作为锁对象,这样可以避免单独再创建一个锁对象。

如果多个地方需要加锁,建议创建一个对象,防止死锁。

NSObject *obj = [NSObject new];

@synchronized(obj){
            
    // 需要锁定的代码   
}

2.自旋锁

加了自旋锁,当新线程访问代码时,如果发现有其他线程正在锁定代码,新线程会用死循环的方式,一直等待锁定的代码执行完成。相当于不停尝试执行代码,比较消耗性能。

属性修饰atomic本身就有一把自旋锁。

属性修饰nonatomic 和 atomic

nonatomic 非原子属性(非线程安全),同一时间可以有很多线程读和写,效率高

atomic 原子属性(线程安全),保证同一时间只有一个线程能够写入
(但是同一个时间多个线程都可以取值),atomic 本身就有一把锁(自旋锁),需要消耗大量的资源

九.死锁问题

@synchronized产生死锁场景

     /** A锁 */
     static NSString* A = @"A";
    
    /** B锁 */
     static NSString* B = @"B";
    dispatch_async(queue, ^{
          //  NSLog(@"%@",[self sourceOut]) ;
        @synchronized(A){
            NSLog(@"锁A0");
            sleep(2);
            @synchronized(B){
                NSLog(@"锁B0");
            }
        }
        });
    
    dispatch_async(queue, ^{
        @synchronized(B){
            NSLog(@"锁B1");
           
            @synchronized(A){
                NSLog(@"锁A1");
            }
        }
    });
打印:2018-04-06 15:35:56.206903+0800 COCOCOCO[13309:566143] 锁A0
2018-04-06 15:35:56.206939+0800 COCOCOCO[13309:566145] 锁B1

NSLock产生死锁场景

[self.lock lock];
//由于当前线程加锁,现在再次加同样的锁,需等待当前线程解锁,把当前线程挂起,不能解锁
[self.lock lock];
[_lock unlock];
[_lock unlock];

GCD产生死锁场景

参考文章:

GCD主要参考文章:https://www.jianshu.com/p/2d57c72016c6

其它内容:https://blog.csdn.net/hejiasu/article/details/82768913

http://www.cocoachina.com/ios/20170707/19769.html

https://blog.csdn.net/hubercui/article/details/79833985

上一篇 下一篇

猜你喜欢

热点阅读