iOS_多线程

  • 程序:每一个应用程序App都称为一个程序。
  • 进程:正在运行的一个应用程序就是一个进程,相当于一个任务,进程拥有全部的资源,负责资源的调度和分配。
  • 线程:线程就是程序中一个单独的代码块(单独的功能)。
  • 主线程:每个正在运行的程(即进程)序,至少包含一个线程,这个线程就叫做主线程。
  • 子线程:iOS允许用户自己开辟新的线程,相对于主线程而言,这些新的线程都可以称为子线程。
  • 注意:子线程与主线程是独立的运行单元,各自执行互不影响,可以并发执行。

这里写图片描述


  • 验证当前线程是否是主线程
BOOL isResult = [[NSThread currentThread]isMainThread];
NSLog(@"%d",isResult);
//结果为1,为主线程
  • 为了防止线程同步,即两条线程同时执行同一个任务,处理同一条数据,例如买票程序;此时就需要对线程加解锁,线程开始加锁,结束时解锁。

// 加锁,避免线程同步
    NSLock *oneLock = [[NSLock alloc]init];//线程锁对象
    [oneLock lock]; // 加锁
    [oneLock unlock]; // 解锁
  • 线程都是自带runLoop,主线程的默认打开的,子线程默认是关闭的,因此记得打开子线程runLoop

PThread

  • C语言版的简单开辟子线程
    代码示例:
#import <pthread.h>  // 导入此框架,验证PThread
// 开辟子线程的方式:
- (void)creatThreadWithPThread{
    pthread_t thread1;
    NSString *identification = @"threadOne"; // 线程标识符
    // (__bridge void *)(identification) 将OC的id类型转化为 void* 类型
    pthread_create(&thread1, NULL, threadFunc, (__bridge void *)(identification));
    // 参数一:线程变量;参数二:线程属性,这里没有进行设定;参数三:子线程将要执行的方法;参数四:执行方法传递的参数,这里传递的是线程标识符,
    // thread1  pthread_t   0x70000ad70000  0x000070000ad70000

}
// 线程将要执行的方法
void *threadFunc(void *paragram){
    BOOL isResult = [[NSThread currentThread]isMainThread];
    // (__bridge NSString *)(paragram) 将void*类型转化为OC的id类型
    NSLog(@"传递的标识符为:%@",(__bridge NSString *)(paragram));
    NSLog(@"当前线程为:%@",isResult?@"主线程":@"子线程");
    pthread_t thisThrad = pthread_self();
    // thisThrad    pthread_t   0x70000ad70000  0x000070000ad70000
    pthread_cancel(thisThrad);
    NSLog(@"-这句话执行不到-");
    return NULL;
}
// 经过断点测试,pthread_self()获取的是执行该方法的子线程。

NSThread

  • NSThread是对PThread进行OC层次的封装。
  • NSThread创建子线程方式一:使用类方法进行创建。
  • NSThread创建子线程方式二:使用对象方法进行创建。

使用NSThread的第一种方式,使用类方法创建

  • 在ios10.0之后,类方法创建存在block方法创建。
  • 代码示例如下:
// NSThreadAndNSObjectViewController.m

// NSThread创建子线程方式一:使用类方法进行创建
- (void)creatThreadWithNSThreadFirst{
    NSDictionary *dict = @{@"key":@"NSThreadFirst"};
    [NSThread detachNewThreadSelector:@selector(NSThreadFirstFunc:) toTarget:self withObject:dict];
    // ios10.0 之后可以利用block的模式添加子线程
    [NSThread detachNewThreadWithBlock:^{
        BOOL isResult = [NSThread isMultiThreaded];
        NSLog(@"类方式创建-block-当前线程为:%@",isResult?@"子线程":@"主线程");
    }];
}
- (void)NSThreadFirstFunc:(id)argument{
    if ([argument isKindOfClass:[NSDictionary class]]) {
        NSLog(@"%@",[argument valueForKey:@"key"]);
    }
    BOOL isResult = [NSThread isMultiThreaded]; //线程是否是子线程
    NSLog(@"类方式创建-当前线程为:%@",isResult?@"子线程":@"主线程");
}

使用NSThread的第二种方式

  • 记得手动启动线程,利用NSThread对象方法创建的子线程需要。
  • 手动创建的子线程不会自动添加释放池,因此需要我们自己手动添加自动释放池。
  • 在ios10.0之后,对象创建子线程也有block模式。
// 自动释放池
@autoreleasepool {}
  • threadPriority线程优先级,值范围0.0~1.0,默认0.5。
  • 代码示例:
// NSThreadAndNSObjectViewController.m

// NSThread创建子线程方式二:使用对象方法进行创建
- (void)creatThreadWithNSThreadSecond{
    NSDictionary *dict = @{@"key":@"NSThreadSecond"};
    NSThread *thread1 = [[NSThread alloc]initWithTarget:self selector:@selector(NSThreadSecond:) object:dict];
    // ios10.0 之后,可以利用block进行子线程的创建
    NSThread *thread2 = [[NSThread alloc]initWithBlock:^{
        BOOL isResult = [NSThread isMultiThreaded];
        [NSThread exit]; // 线程强制终止,写在那个子线程方法内,终止哪个子线程
        NSLog(@"对象方法创建-block-当前线程为:%@",isResult?@"子线程":@"主线程");
    }];
    // 线程优先级,值范围0.0~1.0,默认0.5
    thread1.threadPriority = 0.2;
    thread2.threadPriority = 1.0;
    // 对象方式创建子线程,需手动开启线程。
    [thread1 start];
    [thread2 start];
    self.thread = thread1;
}
- (void)NSThreadSecond:(id)argument{
    // 手动开辟的子线程是不会自动添加释放池的,需要手动添加自动释放池
    @autoreleasepool {
        if ([argument isKindOfClass:[NSDictionary class]]) {
            NSLog(@"%@",[argument valueForKey:@"key"]);
        }
        // 线程取消
        for (int i = 0; i<2000; i++) {
            NSLog(@"%d",i);
            if (self.thread.cancelled) {
                break;
            }
            if (i == 200) {
                [self.thread cancel];
            }
        }
        BOOL isResult = [NSThread isMultiThreaded];
        NSLog(@"对象方式创建-当前线程为:%@",isResult?@"子线程":@"主线程");
    }
}
  • 线程的手动终止- (void)cancel;,取消不了正在进行的任务(因此一般应用于NSOperationQueue用来结束没有来得急执行的任务), thread的状态就变成了cancelled,结合cancelled来终止任务。
  • 强制终止任务exit用法,在哪个线程中执行,就是终止哪个线程。
[NSThread exit];

NSObject

  • 因为所有的类都继承自根类,因此创建线程时,直接用self调用对应的方法即可。
  • 在应用程序打开的时候,系统会自动为主线程操作的方法中创建一个自动释放池;但是我们手动开辟的子线程是不会自动添加释放池的,我们需要手动添加自动释放池。
  • 利用子线程处理数据之后,对于界面的刷新以及UI控件的添加显示都在主线程中操作。
  • 代码示例:
// NSThreadAndNSObjectViewController.m

#pragma mark - NSObject
// NSObject创建子线程方式
- (void)creatThreadWithNSObject{
    [self performSelectorInBackground:@selector(NSObjectThreadFunc) withObject:nil];
}
- (void)NSObjectThreadFunc{
    // 当程序打开运行时,系统会自动为主线程添加自动释放池,对于子线程需要我们自己添加。
    @autoreleasepool {
        BOOL isResult = [NSThread isMultiThreaded];
        NSLog(@"NSObject方式创建-当前线程为:%@",isResult?@"子线程":@"主线程");
        // 利用子线程处理数据之后,对于界面的刷新以及UI控件的添加显示都在主线程中操作
        [self performSelectorOnMainThread:@selector(NSObjectMainThread) withObject:nil waitUntilDone:YES];
    }
}
- (void)NSObjectMainThread{

}

定时器的使用

  • 在子线程中使用定时器,需要手动启动执行定时器任务。
  • 在子线程当中使用定时器,需要手动开启执行定时器任务,也就是需要手动开启事件循环,定时器生效—执行任务
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(startTimerTask) userInfo:nil repeats:YES];
//    在子线程当中使用定时器,需要手动开启执行定时器任务,也就是需要手动开启事件循环,定时器生效---执行任务
[[NSRunLoop currentRunLoop] run];

// 在ARC环境下重置timer,注意添加retain

NSOperationQueue

  • 创建任务队列需要以下几步:
    • 1、创建子线程任务(存在两种方式)
    • 2、设置多个子线程任务之间的依赖关系(可以不设置,如果设置了,避免循环依赖)
    • 3、创建任务队列
    • 4、设置任务队列同步执行任务最大数(最大并发数, 1代表不设置最大并发数)
    • 5、将子线程任务添加到任务队列中
    • 注意:第二步必须在第一步后面,第四步必须在第三步后面,第五步必须在最后。
    • 线程任务创建之后,直接开启任务([invocationOperationOne start];),任务在主线程中进行,如果将任务添加到任务队列中,则是在子线程中执行
  • 在任务队列中放的都是 NSOperation 子类的对象(NSBlockOperation、NSInvocationOperation)。
  • NSOperation是对GCD进行OC层次的封装, NSOperationQueue功能更多,封装进去KVO,可以通过KVO对每个任务进行观察,是否完成,是否取消。

使用NSOperationQueue方式创建子线程

创建任务、设置任务间的依赖关系

  • 创建任务存在两种方式(NSBlockOperation、NSInvocationOperation),示例代码如下:
//    1、创建子线程任务(存在两种方式)
    // 方式1:NSBlockOperation
    NSBlockOperation *blockOperationOne = [NSBlockOperation blockOperationWithBlock:^{
        // 子线程将要执行的方法
        NSLog(@">>blockOperationOne");
    }];
    NSBlockOperation *blockOperationTwo = [NSBlockOperation blockOperationWithBlock:^{
        // 子线程将要执行的方法
        NSLog(@">>blockOperationTwo");
    }];

    // 方式2:NSInvocationOperation
    NSInvocationOperation *invocationOperationOne = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(invocationOperationOneFunc) object:nil];
    NSInvocationOperation *invocationOperationTwo = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(invocationOperationTwoFunc) object:nil];
    // 注意:任务创建之后,直接开启任务([invocationOperationOne start];),任务在主线程中进行,如果将任务添加到任务队列中,则是在子线程中执行
  • 设置多个子线程任务之间的依赖关系(可以不设置,如果设置了,避免循环依赖),示例代码如下:
//    2、设置多个子线程任务之间的依赖关系(可以不设置,如果设置了,避免循环依赖)
    [invocationOperationOne addDependency:invocationOperationTwo];
    // One 依赖于 Two,Two执行完成之后,执行One

创建任务队列、设置任务队列最大并发数

  • 最大并发数设置为1,代表不设置。
  • 示例代码如下:
//    3、创建任务队列
    NSOperationQueue *quere = [[NSOperationQueue alloc]init];

//    4、设置任务队列同步执行任务最大数(最大并发数, 1代表不设置最大并发数)
    [quere setMaxConcurrentOperationCount:2];
    // 设置为n,代表n个任务)同时执行(同时执行的任务之间不存在依赖关系),设置为1,则会按照添加顺序,顺序执行

将任务添加到任务队列中

//    5、将子线程任务添加到任务队列中
    [quere addOperation:invocationOperationOne];
    [quere addOperation:invocationOperationTwo];
    [quere addOperation:blockOperationOne];
    [quere addOperation:blockOperationTwo];
    // 如果任务之间没有依赖关系,且最大并发数设置为1,则将会按照任务添加到队列中的顺序执行
  • 如果设置最大并发数为1,而各个任务之间不存在任何依赖关系,此时任务将会按照添加到队列中的先后顺序执行。
  • 如果设值最大并发数为n,则会每次同时执行n个队列中的任务,按照任务添加的先后顺序,即同时执行最前面的未被执行的两个任务。
  • 如果第一个任务完成之后而第二任务未完成,则同时执行第二个和第三个任务。
  • 假设存在任务1、任务2、任务3,任务2依赖于任务1,且依次添加到队列中;如果最大并发数为2,则同步执行任务1和任务3,一旦任务1执行完毕,无论任务3是否结束,都将开始执行任务2;如果最大并发数为1,则按照任务1、任务3、任务2的顺序执行。因为任务2依赖于任务1,执行的过程中会先执行任务2之后的下一个任务,相当于将任务2与其下一个任务进行换位执行。

全部代码示例、及运行结果截图

  • 代码示例:
// NSOperationQueueViewController.m
#pragma mark - NSOperationQueue
- (void)creatThreadWithNSOperationQuere{
//    1、创建子线程任务(存在两种方式)
    // 方式1:NSBlockOperation
    NSBlockOperation *blockOperationOne = [NSBlockOperation blockOperationWithBlock:^{
        // 子线程将要执行的方法
        NSLog(@">>blockOperationOne");
    }];
    NSBlockOperation *blockOperationTwo = [NSBlockOperation blockOperationWithBlock:^{
        // 子线程将要执行的方法
        NSLog(@">>blockOperationTwo");
    }];

    // 方式2:NSInvocationOperation
    NSInvocationOperation *invocationOperationOne = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(invocationOperationOneFunc) object:nil];
    NSInvocationOperation *invocationOperationTwo = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(invocationOperationTwoFunc) object:nil];
    // 注意:任务创建之后,直接开启任务([invocationOperationOne start];),任务在主线程中进行,如果将任务添加到任务队列中,则是在子线程中执行

//    2、设置多个子线程任务之间的依赖关系(可以不设置,如果设置了,避免循环依赖)
    [invocationOperationOne addDependency:invocationOperationTwo];
    // One 依赖于 Two,Two执行完成之后,执行One

//    3、创建任务队列
    NSOperationQueue *quere = [[NSOperationQueue alloc]init];

//    4、设置任务队列同步执行任务最大数(最大并发数, 1代表不设置最大并发数)
    [quere setMaxConcurrentOperationCount:2];
    // 设置为2,代表两个任务同时执行(同时执行的任务之间不存在依赖关系),设置为1,则会按照添加顺序,顺序执行

//    5、将子线程任务添加到任务队列中
    [quere addOperation:invocationOperationOne];
    [quere addOperation:invocationOperationTwo];
    [quere addOperation:blockOperationOne];
    [quere addOperation:blockOperationTwo];
    // 如果任务之间没有依赖关系,且最大并发数设置为1,则将会按照任务添加到队列中的顺序执行
}

- (void)invocationOperationOneFunc{
    NSLog(@">>子线程:invocationOperationOne调用的方法:%s",__func__);
}

- (void)invocationOperationTwoFunc{
    [NSThread sleepForTimeInterval:10]; // 线程10秒后执行
    NSLog(@">>子线程:invocationOperationTwo调用的方法:%s",__func__);
}
  • 运行结果:
    • 1、最大并发数为1,invocationOperationTwo任务10秒后执行,One 依赖于 Two
      这里写图片描述
    • 2、最大并发数为2,invocationOperationTwo任务10秒后执行,One 依赖于 Two
      图片2

GCD(Grand Central Dispatch)

  • 无论是方法还是其它,均以dispatch_开头,与其有关的枚举均以DISPATCH_QUEUE_开头
  • 是苹果主推的一种多线程计数,Apple自己封装,是一套纯C的代码,都是C语言的API,抽象程度最高,执行效率最高(1.C语言 2.充分利用设备多核的特性)
  • 使用步骤:
    • 创建任务队列(串行或者并行)
    • 添加任务(同步或异步)
    • MRC下需要释放任务队列

创建串行队列

  • 串行队列标识:DISPATCH_QUEUE_SERIAL
  • 只有在上一个任务结束之后,才会执行下一个任务
  • 代码示例:
#pragma mark - 串行队列
- (void)creatSerialQuere{
    // 1.串行队列有两种获取方式
    // 1.1串行队列获取方式一(系统创建):
    // 使用系统创建好的串行队列,往队列中添加的任务是在主线程中执行的;
    dispatch_queue_t queueSerialFirst = dispatch_get_main_queue();

    // 1.2串行队列获取方式二(手动创建):
    // 参数一:队列的唯一标识,一般以反域名的方式来标识;参数二:设置队列方式,即串行还是并行,DISPATCH_QUEUE_SERIAL 代表串行队列
    dispatch_queue_t queueSerialSecond = dispatch_queue_create("com.wangsk.second", DISPATCH_QUEUE_SERIAL);

    // 2. 添加任务
    // 2.1 添加异步任务
    dispatch_async(queueSerialSecond, ^{
        NSLog(@"2.1串行队列中,添加异步任务,任务内容");
        BOOL isResult = [[NSThread currentThread]isMainThread];
        NSLog(@"2.1当前线程为:%@",isResult?@"主线程":@"子线程");
    });
    // 2.2 添加同步任务
    dispatch_sync(queueSerialSecond, ^{
        NSLog(@"2.2串行队列中,添加同步任务,任务内容");
        BOOL isResult = [[NSThread currentThread]isMainThread];
        NSLog(@"2.2当前线程为:%@",isResult?@"主线程":@"子线程");
    });

    // 3. 释放队列,MRC模式下需要
//    dispatch_release(queueSerialSecond);

}
/*
执行结果:
2017-08-23 20:40:41.627 Multi-threaded[36375:2254778] 2.1串行队列中,添加异步任务,任务内容
2017-08-23 20:40:41.627 Multi-threaded[36375:2254778] 2.1当前线程为:子线程
2017-08-23 20:40:41.627 Multi-threaded[36375:2254744] 2.2串行队列中,添加同步任务,任务内容
2017-08-23 20:40:41.628 Multi-threaded[36375:2254744] 2.2当前线程为:主线程
*/

创建并行队列

  • 并行队列标识:DISPATCH_QUEUE_CONCURRENT
  • 所有的任务并发执行,也就是并行队列中的任务同时执行
  • 代码示例:
#pragma mark - 并行队列
 - (void)creatConcurrentQuere{
    // 1. 并行队列有两种获取方式
    // 1.1 并行队列获取方式一(系统创建):
    // 参数一:队列优先级;参数二:预留参数
    dispatch_queue_t concurrentQuereFirst = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    // 1.2 并行队列获取方式二(手动创建):
    dispatch_queue_t concurrentQuereSecond = dispatch_queue_create("com.wangsk.concurrent", DISPATCH_QUEUE_CONCURRENT);

    // 2. 添加任务
    // 2.1添加异步任务
    dispatch_async(concurrentQuereSecond, ^{
        NSLog(@"2.1并行队列,添加异步任务,任务内容");
        BOOL isResult = [[NSThread currentThread]isMainThread];
        NSLog(@"2.1当前线程为:%@",isResult?@"主线程":@"子线程");
        //如果执行完子线程任务之后,回到主线程去执行任务 异步任务
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"2.1返回主线程,执行UI界面刷新等操作");
            BOOL isResult = [[NSThread currentThread]isMainThread];
            NSLog(@"2.1-当前线程为:%@",isResult?@"主线程":@"子线程");
        });
    });
    // 2.2添加同步任务
    dispatch_sync(concurrentQuereSecond, ^{
        NSLog(@"2.2并行队列,添加同步任务,任务内容");
        BOOL isResult = [[NSThread currentThread]isMainThread];
        NSLog(@"2.2当前线程为:%@",isResult?@"主线程":@"子线程");

    });

    // 3. 释放队列,MRC模式下需要
//    dispatch_release(concurrentQuereSecond);

}
/*
 执行结果
2017-08-24 10:54:49.617 Multi-threaded[1191:47267] 2.2并行队列,添加同步任务,任务内容
2017-08-24 10:54:49.617 Multi-threaded[1191:47322] 2.1并行队列,添加异步任务,任务内容
2017-08-24 10:54:49.617 Multi-threaded[1191:47267] 2.2当前线程为:主线程
2017-08-24 10:54:49.617 Multi-threaded[1191:47322] 2.1当前线程为:子线程
2017-08-24 10:54:49.624 Multi-threaded[1191:47267] 2.1返回主线程,执行UI界面刷新等操作
2017-08-24 10:54:49.624 Multi-threaded[1191:47267] 2.1-当前线程为:主线程

*/

总结

  • 并行:就是队列里面的任务(代码块,block)不是一个个执行,而是并发执行,也就是可以同时执行的意思
  • 串行:队列里面的任务一个接着一个执行,要等前一个任务结束,下一个任务才可以执行
  • 异步:具有新开线程的能力
  • 同步:不具有新开线程的能力,只能在当前线程执行任务

  • 并行+异步:就是真正的并发,新开有有多个线程处理任务,任务并发执行(不按顺序执行)

  • 串行+异步:新开一个线程,任务一个接一个执行,上一个任务处理完毕,下一个任务才可以被执行
  • 并行+同步:不新开线程,任务一个接一个执行
  • 串行+同步:不新开线程,任务一个接一个执行

  • 添加同步任务:

    • 添加同步任务,到系统创建的串行队列中,会造成线程死锁
    • 添加同步任务,到手动创建的串行队列中,执行任务在主线程。
    • 添加同步任务,到系统创建的并行队列中,执行任务在主线程。
    • 添加同步任务,到手动创建的并行队列中,执行任务在主线程。
  • 添加异步任务:
    • 添加异步任务,到系统创建的串行队列中,执行任务在主线程。
    • 添加异步任务,到手动创建的串行队列中,执行任务在子线程
    • 添加异步任务,到系统创建的并行队列中,执行任务在子线程。
    • 添加异步任务,到手动创建的并行队列中,执行任务在子线程。
  • 总的来说:
    • 添加同步任务,不管在串行还是在并行队列中,所执行的任务都是在主线程中操作。
    • 添加同步任务到系统创建的串行队列中,将会造成线程死锁。
    • 添加异步任务,如果添加到系统创建的串行队列中,那么,所执行的任务都是在主线程中操作;而将异步任务添加到手动创建的队列中,那么,所执行的任务都是在子线程中操作。因此,一般使用手动创建的串行队列。

线程死锁

  • 两个线程相互等待,两个线程都不执行任务(队列遵循FIFO)
  • 代码示例:
#pragma mark - 线程死锁
- (void)testThreadLock{
    dispatch_queue_t quere = dispatch_get_main_queue();
    dispatch_sync(quere, ^{
        NSLog(@"输出>>>>");
    });
}
  • 为了防止线程死锁,避免在系统创建的串行队列中,添加同步任务。

线程安全

iOS_线程安全

加锁

  • 互斥锁:@synchronized (<#token#>) {// 互斥操作 },括号(<#token#>)内是锁对象,设置锁对象时必须保证多个线程访问的都是同一个对象,锁对象必须是唯一的,还必须是id类型;操作尽量要少些,因为代码越多,效率越低。因此,在互斥操作中最好只写对互斥数据的操作。
  • 递归锁:用来解决递归调用,进行加锁。
  • 条件锁:可以设置条件来进行加锁或者解锁。
  • 分布锁:对于文件系统可以进行访问。
  • 自旋锁:原子性(atomic),加的就是自旋锁,比互斥锁的效率高,程序没有进入休眠或者阻塞,相当于在执行一个很大的循环,没有休眠,执行任务,因此说效率要高一点。

信号量机制

  • 在GCD中提供了一种信号机制,也可以解决资源抢占问题(和同步锁的机制并不一样)
  • GCD中信号量是dispatch_semaphore_t类型,支持信号通知和信号等待。
  • 每当发送一个信号通知,则信号量+1;每当发送一个等待信号时信号量-1;如果信号量为0则信号会处于等待状态,直到信号量大于0开始执行。
  • 根据这个原理我们可以初始化一个信号量变量,默认信号量设置为1,每当有线程进入“加锁代码”之后就调用信号等待命令(此时信号量为0)开始等待,此时其他线程无法进入,执行完后发送信号通知(此时信号量为1),其他线程开始进入执行,如此一来就达到了线程同步目的。
// 代码示例:
#pragma mark - 线程安全
- (void)threadSafe{
    // 如果利用信号量进行线程的加解锁,信号量初始值应当设置为1,这里为了测试,设置的值为3,orig为3
    dispatch_semaphore_t semapore_t = dispatch_semaphore_create(3); // value = 3,orig = 3

    // 发出等待信号,信号量-1,value值发生改变,value为0,加锁
    dispatch_semaphore_wait(semapore_t, DISPATCH_TIME_FOREVER); // value = 2,orig = 3

    // 发出信号,信号量+1,value值发生改变,解锁
    dispatch_semaphore_signal(semapore_t); // value = 3,orig = 3
}

代码地址:
https://github.com/FlyingKuiKui/Multi-threaded.git

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值