多线程

作者: 小石头呢 | 来源:发表于2019-06-09 09:52 被阅读0次

一.多线程的基本概念

  • 进程:可以理解成一个运行中的应用程序,是系统进行资源分配和调度的基本单位,是操作系统结构的基础,主要管理资源。

  • 线程:是进程的基本执行单元,一个进程对应多个线程。同一进程中的多条线程将共享该进程中的全部系统资源,但是自有调用堆栈和寄存器环境。

  • 主线程:处理UI,所有更新UI的操作都必须在主线程上执行。不要把耗时操作放在主线程,会卡界面。

  • 多线程:在同一时刻,一个CPU只能处理1条线程,但CPU可以在多条线程之间快速的切换,只要切换的足够快,就造成了多线程多线程并发执行的假象。

  • 使用多线程的目的:将耗时的操作放在后台执行!

  • 注意:如果线程非常多,CPU会在N多线程之间切换,CPU消耗就会特别大,每条线程被调用额频率就会被降低,所有要适当使用多线程

二.多线程的生命周期

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

  • 新建:实例化线程对象。

  • 就绪:向线程对象发送start消息,线程对象被加入可调度线程池等待CPU调度。

  • 运行:CPU 负责调度可调度线程池中线程的执行。线程执行完成之前,状态可能会在就绪和运行之间来回切换。就绪和运行之间的状态变化由CPU负责,程序员不能干预。

  • 阻塞:当满足某个预定条件时,可以使用休眠或锁,阻塞线程执行。sleepForTimeInterval(休眠指定时长),sleepUntilDate(休眠到指定日期),@synchronized(self):(互斥锁)。

  • 死亡:正常死亡,线程执行完毕。非正常死亡,当满足某个条件后,在线程内部中止执行/在主线程中止线程对象。

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

四.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的好处

  • GCD 可用于多核的并行运算
  • GCD 会自动利用更多的 CPU 内核(比如双核、四核)
  • GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
  • 程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码

2.队列和任务

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

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

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

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

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

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

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

3.GCD的使用

  • 1.创建一个队列(串行队列或并发队列)

  • 2.将任务追加到任务的等待队列中,然后系统就会根据任务类型执行任务(同步执行或异步执行)

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

相关文章

  • iOS多线程 NSOperation

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • iOS多线程 pthread、NSThread

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • iOS多线程: GCD

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • iOS多线程运用

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • iOS多线程基础

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • 多线程介绍

    一、进程与线程 进程介绍 线程介绍 线程的串行 二、多线程 多线程介绍 多线程原理 多线程的优缺点 多线程优点: ...

  • iOS进阶之多线程管理(GCD、RunLoop、pthread、

    深入理解RunLoopiOS多线程--彻底学会多线程之『GCD』iOS多线程--彻底学会多线程之『pthread、...

  • iOS多线程相关面试题

    iOS多线程demo iOS多线程之--NSThread iOS多线程之--GCD详解 iOS多线程之--NSOp...

  • 多线程之--NSOperation

    iOS多线程demo iOS多线程之--NSThread iOS多线程之--GCD详解 iOS多线程之--NSOp...

  • iOS多线程之--NSThread

    iOS多线程demo iOS多线程之--NSThread iOS多线程之--GCD详解 iOS多线程之--NSOp...

网友评论

      本文标题:多线程

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