每日一問13——多線程之NSOperation與NSOperationQueue

NSOperation

看一下官方的介紹:

The NSOperation class itself is an abstract base class that must be subclassed in order to do any useful work.

NSOperation是一個(gè)抽象基類,我們需要使用它的子類來完成工作。從使用上來說NSOperation就是對(duì)GCDblock的封裝,它表示一個(gè)要被執(zhí)行的任務(wù)。
GCD相比,NSOperation可以更好的控制任務(wù),包括暫停,繼續(xù),取消這類操作并且提供了當(dāng)前任務(wù)的狀態(tài)供開發(fā)者監(jiān)聽。而在GCD中要實(shí)現(xiàn)這些需求可能會(huì)需要其他很多復(fù)雜的代碼。

NSOperation基本功能
1.基本的操作和狀態(tài)

先看一下NSOperation.h中的NSOperation類。

- (void)start;
- (void)main;

@property (readonly, getter=isCancelled) BOOL cancelled;
- (void)cancel;

@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;
@property (readonly, getter=isConcurrent) BOOL concurrent; // To be deprecated; use and override 'asynchronous' below
@property (readonly, getter=isAsynchronous) BOOL asynchronous NS_AVAILABLE(10_8, 7_0);
@property (readonly, getter=isReady) BOOL ready;

NSOperation提供了任務(wù)的狀態(tài),執(zhí)行,完畢,取消等。并且提供了一個(gè)start方法,這表明NSOperation是可以直接執(zhí)行的。這里要注意NSOperation執(zhí)行是一個(gè)同步操作,任務(wù)會(huì)執(zhí)行在當(dāng)前運(yùn)行的線程上。

2.依賴關(guān)系
- (void)addDependency:(NSOperation *)op;
- (void)removeDependency:(NSOperation *)op;

@property (readonly, copy) NSArray<NSOperation *> *dependencies;

NSOperation還提供了一個(gè)依賴功能,通過依賴可以控制任務(wù)執(zhí)行的順序。

3.任務(wù)優(yōu)先級(jí)
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8
};

@property NSOperationQueuePriority queuePriority;

GCD不同,GCD只能設(shè)置隊(duì)列的優(yōu)先級(jí),而NSOperation及其子類可以設(shè)置任務(wù)的優(yōu)先級(jí)。需要注意的是,NSOperationQueue也不能完全保證優(yōu)先級(jí)高的任務(wù)一定先執(zhí)行。

系統(tǒng)提供的子類
NSInvocationOperation

這個(gè)類的使用類似于給UIbutton添加一個(gè)方法。需要一個(gè)對(duì)象和一個(gè)Selector。

NSInvocationOperation *inOp = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testInOp) object:nil];
NSLog(@"begin");
[inOp start];
NSLog(@"end");

- (void)testInOp {
    NSLog(@"%@",[NSThread currentThread]);
}

打印結(jié)果:

2017-09-19 16:12:55.938 learn_09_NSOperation[3507:146749] begin
2017-09-19 16:12:55.938 learn_09_NSOperation[3507:146749] <NSThread: 0x60000006e700>{number = 1, name = main}
2017-09-19 16:12:55.939 learn_09_NSOperation[3507:146749] end

可以看出,這個(gè)任務(wù)相是同步執(zhí)行的。再修改一下例子:

NSInvocationOperation *inOp = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testInOp) object:nil];
    NSLog(@"begin");
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [inOp start];
    });
    NSLog(@"end");

打印結(jié)果

2017-09-19 16:14:44.266 learn_09_NSOperation[3536:147731] begin
2017-09-19 16:14:44.266 learn_09_NSOperation[3536:147731] end
2017-09-19 16:14:44.267 learn_09_NSOperation[3536:147761] <NSThread: 0x60000007c240>{number = 3, name = (null)}

可以看出,任務(wù)可以直接在子線程啟動(dòng),就變成一個(gè)異步執(zhí)行的任務(wù)。其實(shí)這里還是在其他線程中同步執(zhí)行了該任務(wù)

NSBlockOperation

與之前的NSInvocationOperation比較,NSBlockOperation在使用上更加的方便,它提供一個(gè)block代碼塊來執(zhí)行任務(wù)。

NSBlockOperation *bOp = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
    }];
[bOp start];

執(zhí)行結(jié)果:<NSThread: 0x60800006f280>{number = 1, name = main}
我們可以看出基本使用上與NSInvocationOperation結(jié)果一致。但不同的是NSBlockOperation支持了并發(fā)執(zhí)行一個(gè)或多個(gè)block。

NSBlockOperation *bOp = [[NSBlockOperation alloc] init];
    [bOp addExecutionBlock:^{
        NSLog(@"1--%@",[NSThread currentThread]);
        NSLog(@"2--%@",[NSThread currentThread]);
        NSLog(@"3--%@",[NSThread currentThread]);
    }];
    [bOp addExecutionBlock:^{
        NSLog(@"4--%@",[NSThread currentThread]);
        NSLog(@"5--%@",[NSThread currentThread]);
        NSLog(@"6--%@",[NSThread currentThread]);
    }];
    [bOp addExecutionBlock:^{
        NSLog(@"7--%@",[NSThread currentThread]);
        NSLog(@"8--%@",[NSThread currentThread]);
        NSLog(@"9--%@",[NSThread currentThread]);
    }];
    [bOp start];

執(zhí)行結(jié)果:

2017-09-19 16:22:53.392 learn_09_NSOperation[3631:152054] 1--<NSThread: 0x60800007ad80>{number = 1, name = main}
2017-09-19 16:22:53.392 learn_09_NSOperation[3631:152054] 2--<NSThread: 0x60800007ad80>{number = 1, name = main}
2017-09-19 16:22:53.392 learn_09_NSOperation[3631:152054] 3--<NSThread: 0x60800007ad80>{number = 1, name = main}
2017-09-19 16:22:53.392 learn_09_NSOperation[3631:152091] 4--<NSThread: 0x60800007c440>{number = 3, name = (null)}
2017-09-19 16:22:53.392 learn_09_NSOperation[3631:152089] 7--<NSThread: 0x608000266300>{number = 4, name = (null)}
2017-09-19 16:22:53.393 learn_09_NSOperation[3631:152091] 5--<NSThread: 0x60800007c440>{number = 3, name = (null)}
2017-09-19 16:22:53.393 learn_09_NSOperation[3631:152089] 8--<NSThread: 0x608000266300>{number = 4, name = (null)}
2017-09-19 16:22:53.394 learn_09_NSOperation[3631:152091] 6--<NSThread: 0x60800007c440>{number = 3, name = (null)}
2017-09-19 16:22:53.394 learn_09_NSOperation[3631:152089] 9--<NSThread: 0x608000266300>{number = 4, name = (null)}

從中我們可以看出每一個(gè)代碼塊執(zhí)行在一條線程上,每條線程之間彼此無關(guān),任務(wù)執(zhí)行順序是并發(fā)執(zhí)行的。

為了更好的操作這些任務(wù),我們需要結(jié)合NSOperationQueue來管理這些任務(wù)。這樣,我們任務(wù)隊(duì)列的概念就結(jié)合起來了。

自定義NSOperation的子類

自定義串行NSOperation,只需要在子類中重寫main函數(shù)
自定義并行NSOperation,需要子類實(shí)現(xiàn)start,main,isExecuting,isFinished,isConcurrent,isAsynchronous方法
一般來說我們使用系統(tǒng)提供的子類就可以完成絕大部分需求了。關(guān)于自定義子類可參見這篇文章NSOperation 自定義子類實(shí)現(xiàn)非并發(fā)和并發(fā)操作

NSOperationQueue

1.隊(duì)列的基本使用

NSOperationQueue類似于GCD中的隊(duì)列。我們知道GCD中的隊(duì)列有三種:主隊(duì)列、串行隊(duì)列和并發(fā)隊(duì)列NSOperationQueue更簡單,只有兩種:主隊(duì)列非主隊(duì)列。

默認(rèn)情況下,我們創(chuàng)建的NSOperationQueue隊(duì)列都是非主隊(duì)列。
我們可以通過NSOperationQueue.mainQueue來獲取主隊(duì)列。

NSOperationQueue的主隊(duì)列是串行隊(duì)列,而且其中所有NSOperation都會(huì)在主線程中執(zhí)行。

對(duì)于非主隊(duì)列來說,一旦一個(gè)NSOperation被放入其中,那這個(gè)NSOperation一定是并發(fā)執(zhí)行的。因?yàn)?code>NSOperationQueue會(huì)為每一個(gè)NSOperation創(chuàng)建線程并調(diào)用它的start方法。
但這并不意味著NSOperationQueue不支持串行執(zhí)行。NSOperationQueue提供了maxConcurrentOperationCount屬性,讓我們?cè)O(shè)置隊(duì)列中的最大并發(fā)數(shù)。當(dāng)設(shè)置為1時(shí),隊(duì)列會(huì)按串行方式執(zhí)行里面的任務(wù)。

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.maxConcurrentOperationCount = 1;
    for (int i=0; i < 10; ++i) {
        NSBlockOperation *bop = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"%d----%@",i,[NSThread currentThread]);
        }];
        
        [queue addOperation:bop];
    }

打印結(jié)果:

2017-09-19 16:38:09.401 learn_09_NSOperation[3763:158562] 0----<NSThread: 0x608000079500>{number = 3, name = (null)}
2017-09-19 16:38:09.402 learn_09_NSOperation[3763:158562] 1----<NSThread: 0x608000079500>{number = 3, name = (null)}
2017-09-19 16:38:09.402 learn_09_NSOperation[3763:158562] 2----<NSThread: 0x608000079500>{number = 3, name = (null)}
2017-09-19 16:38:09.403 learn_09_NSOperation[3763:158562] 3----<NSThread: 0x608000079500>{number = 3, name = (null)}
2017-09-19 16:38:09.403 learn_09_NSOperation[3763:158562] 4----<NSThread: 0x608000079500>{number = 3, name = (null)}
2017-09-19 16:38:09.405 learn_09_NSOperation[3763:158562] 5----<NSThread: 0x608000079500>{number = 3, name = (null)}
2017-09-19 16:38:09.405 learn_09_NSOperation[3763:158562] 6----<NSThread: 0x608000079500>{number = 3, name = (null)}
2017-09-19 16:38:09.406 learn_09_NSOperation[3763:158562] 7----<NSThread: 0x608000079500>{number = 3, name = (null)}
2017-09-19 16:38:09.406 learn_09_NSOperation[3763:158562] 8----<NSThread: 0x608000079500>{number = 3, name = (null)}
2017-09-19 16:38:09.407 learn_09_NSOperation[3763:158562] 9----<NSThread: 0x608000079500>{number = 3, name = (null)}

從結(jié)果可以看出,在最大并發(fā)為1的情況下,隊(duì)列是異步的串行執(zhí)行的。當(dāng)然如果我們不設(shè)置最大并發(fā)數(shù)或者>1的情況下,系統(tǒng)會(huì)自動(dòng)為每個(gè)任務(wù)分配線程,所有的線程調(diào)度都交給系統(tǒng)控制。

2.任務(wù)管理

由于我們的NSOperation對(duì)象擁有自己的任務(wù)狀態(tài),為了管理隊(duì)列中的NSOperation任務(wù),系統(tǒng)為我們提供了以下方法來獲取隊(duì)列中的任務(wù)。

@property (readonly, copy) NSArray<__kindof NSOperation *> *operations;
@property (readonly) NSUInteger operationCount NS_AVAILABLE(10_6, 4_0);

我們可以獲取到隊(duì)列中的每一個(gè)任務(wù)并操作它。

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    for (int i=0; i < 10; ++i) {
        NSBlockOperation *bop = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"%d----%@",i,[NSThread currentThread]);
        }];

        [queue addOperation:bop];
    }
    NSBlockOperation *op = queue.operations.lastObject;
   NSLog(@"%d",op.isCancelled);
    [op cancel];
    NSLog(@"%d",op.isCancelled);

在執(zhí)行前將任務(wù)取消掉,則該任務(wù)將不會(huì)被執(zhí)行。

當(dāng)然,在任務(wù)較多的情況下,這樣取消任務(wù)實(shí)在是太可愛了。NSOperationQueue 提供了cancelAllOperations方法,幫我們直接取消掉所有的任務(wù)。

3.其他操作
隊(duì)列優(yōu)先級(jí)

像CGD一樣,NSOperationQueue同樣提供了優(yōu)先級(jí)設(shè)置,我們可以根據(jù)不同場景選擇對(duì)應(yīng)的優(yōu)先級(jí)。

typedef NS_ENUM(NSInteger, NSQualityOfService) {
    /* UserInteractive QoS is used for work directly involved in providing an interactive UI such as processing events or drawing to the screen. */
    NSQualityOfServiceUserInteractive = 0x21,
    
    /* UserInitiated QoS is used for performing work that has been explicitly requested by the user and for which results must be immediately presented in order to allow for further user interaction.  For example, loading an email after a user has selected it in a message list. */
    NSQualityOfServiceUserInitiated = 0x19,
    
    /* Utility QoS is used for performing work which the user is unlikely to be immediately waiting for the results.  This work may have been requested by the user or initiated automatically, does not prevent the user from further interaction, often operates at user-visible timescales and may have its progress indicated to the user by a non-modal progress indicator.  This work will run in an energy-efficient manner, in deference to higher QoS work when resources are constrained.  For example, periodic content updates or bulk file operations such as media import. */
    NSQualityOfServiceUtility = 0x11,
    
    /* Background QoS is used for work that is not user initiated or visible.  In general, a user is unaware that this work is even happening and it will run in the most efficient manner while giving the most deference to higher QoS work.  For example, pre-fetching content, search indexing, backups, and syncing of data with external systems. */
    NSQualityOfServiceBackground = 0x09,

    /* Default QoS indicates the absence of QoS information.  Whenever possible QoS information will be inferred from other sources.  If such inference is not possible, a QoS between UserInitiated and Utility will be used. */
    NSQualityOfServiceDefault = -1
} NS_ENUM_AVAILABLE(10_10, 8_0);

@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);
等待執(zhí)行
- (void)waitUntilAllOperationsAreFinished

NSOperationQueue提供了這樣一個(gè)函數(shù),讓調(diào)用該函數(shù)前的所有任務(wù)執(zhí)行完畢以后才會(huì)執(zhí)行后續(xù)的操作

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    for (int i=0; i < 3; ++i) {
        NSBlockOperation *bop = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"%d----%@",i,[NSThread currentThread]);
        }];
        [queue addOperation:bop];
    }
    NSLog(@"wait");
    [queue waitUntilAllOperationsAreFinished];
    NSLog(@"go on");

打印結(jié)果:

2017-09-19 16:58:22.864 learn_09_NSOperation[3908:166871] wait
2017-09-19 16:58:22.865 learn_09_NSOperation[3908:166906] 1----<NSThread: 0x6000002620c0>{number = 3, name = (null)}
2017-09-19 16:58:22.865 learn_09_NSOperation[3908:166907] 2----<NSThread: 0x608000078940>{number = 5, name = (null)}
2017-09-19 16:58:22.865 learn_09_NSOperation[3908:166909] 0----<NSThread: 0x600000262600>{number = 4, name = (null)}
2017-09-19 16:58:22.865 learn_09_NSOperation[3908:166871] go on

當(dāng)任務(wù)執(zhí)行完畢后,才會(huì)繼續(xù)執(zhí)行waitUntilAllOperationsAreFinished下面的代碼,可以看出waitUntilAllOperationsAreFinished會(huì)阻塞當(dāng)前的線程來等待隊(duì)列中任務(wù)執(zhí)行。

暫停隊(duì)列

NSOperationQueue 提供了suspended的屬性,顧名思義這個(gè)屬性用來控制隊(duì)列是否暫停。但它的暫停方法并不是那么直接。官方文檔上是這么說的:

如果這個(gè)值設(shè)置為 NO,那說明這個(gè)隊(duì)列已經(jīng)準(zhǔn)備好了可以執(zhí)行了。如果這個(gè)值設(shè)置為 YES,那么已經(jīng)添加到隊(duì)列中的操作還是可以執(zhí)行了,而后面繼續(xù)添加進(jìn)隊(duì)列中的操作才處于暫停狀態(tài),直到你再次將這個(gè)值設(shè)置為 NO 時(shí),后面加入的操作才會(huì)繼續(xù)執(zhí)行。這個(gè)屬性的默認(rèn)值是 NO。

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    for (int i=0; i < 4; ++i) {
        NSBlockOperation *bop = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"%d",i);
        }];
        if(i == 2) {
            [queue setSuspended:YES];
        }
        [queue addOperation:bop];
    }
//模擬耗時(shí)操作后開啟隊(duì)列
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"re start");
        [queue setSuspended:NO];
    });

打印結(jié)果:

2017-09-19 17:10:23.796 learn_09_NSOperation[3994:171960] 0
2017-09-19 17:10:23.796 learn_09_NSOperation[3994:171959] 1
2017-09-19 17:10:25.796 learn_09_NSOperation[3994:171922] re start
2017-09-19 17:10:25.796 learn_09_NSOperation[3994:171959] 2
2017-09-19 17:10:25.796 learn_09_NSOperation[3994:171960] 3
一個(gè)例子:利用任務(wù)依賴實(shí)現(xiàn)一個(gè)規(guī)定順序的任務(wù)隊(duì)列

假設(shè)我們10個(gè)任務(wù),我們需要異步執(zhí)行,先順序執(zhí)行后5條任務(wù)再順序執(zhí)行前5條任務(wù)。通過NSOperation的依賴功能與NSOperationQueue結(jié)合來滿足這個(gè)需求。(代碼寫得不好,只是為了表現(xiàn)NSOperationQueue的使用靈活與簡便)

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSMutableArray *ops = [NSMutableArray array];
    for (int i = 0; i < 10; ++i) {
        NSBlockOperation *bop = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"%d---%@",i,[NSThread currentThread]);
        }];
        [ops addObject:bop];
    }
    
//
    [ops enumerateObjectsUsingBlock:^(NSBlockOperation *obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if(idx > 0 && idx < 5){
            NSBlockOperation *old = ops[idx - 1];
            [obj addDependency:old];
        }
        else if (idx > 5) {
            NSBlockOperation *old = ops[idx - 1];
            [obj addDependency:old];
        }
        if(idx == ops.count - 1) {
            NSBlockOperation *fst = ops.firstObject;
            [fst addDependency:obj];
        }
    }];
    
    [queue addOperations:ops waitUntilFinished:NO];

打印結(jié)果:

2017-09-19 17:16:33.256 learn_09_NSOperation[4037:174132] 5---<NSThread: 0x608000267c80>{number = 3, name = (null)}
2017-09-19 17:16:33.258 learn_09_NSOperation[4037:174132] 6---<NSThread: 0x608000267c80>{number = 3, name = (null)}
2017-09-19 17:16:33.260 learn_09_NSOperation[4037:174132] 7---<NSThread: 0x608000267c80>{number = 3, name = (null)}
2017-09-19 17:16:33.261 learn_09_NSOperation[4037:174134] 8---<NSThread: 0x608000262d80>{number = 4, name = (null)}
2017-09-19 17:16:33.262 learn_09_NSOperation[4037:174134] 9---<NSThread: 0x608000262d80>{number = 4, name = (null)}
2017-09-19 17:16:33.265 learn_09_NSOperation[4037:174134] 0---<NSThread: 0x608000262d80>{number = 4, name = (null)}
2017-09-19 17:16:33.283 learn_09_NSOperation[4037:174134] 1---<NSThread: 0x608000262d80>{number = 4, name = (null)}
2017-09-19 17:16:33.292 learn_09_NSOperation[4037:174134] 2---<NSThread: 0x608000262d80>{number = 4, name = (null)}
2017-09-19 17:16:33.298 learn_09_NSOperation[4037:174132] 3---<NSThread: 0x608000267c80>{number = 3, name = (null)}
2017-09-19 17:16:33.299 learn_09_NSOperation[4037:174134] 4---<NSThread: 0x608000262d80>{number = 4, name = (null)}

可以看出先依次執(zhí)行了5-9,再依次執(zhí)行了0-4。并且在設(shè)置了依賴關(guān)系后,隊(duì)列的線程調(diào)度也會(huì)自動(dòng)變化。保證了程序性能。

NSOperation與GCD比較

NSOperation是CGD的封裝,相比于GCD而言,NSOperation可以更加方便的控制任務(wù),設(shè)置依賴,執(zhí)行,暫停,取消,控制優(yōu)先級(jí)等。NSOperation是對(duì)線程的高度抽象,在項(xiàng)目中使用它,會(huì)使項(xiàng)目的程序結(jié)構(gòu)更好,子類化NSOperation的設(shè)計(jì)思路,是具有面向?qū)ο蟮膬?yōu)點(diǎn)(復(fù)用、封裝),使得實(shí)現(xiàn)是多線程支持,而接口簡單。

GCD以block為單位,代碼簡潔。同時(shí)GCD中的隊(duì)列、組、信號(hào)量、source、barriers都是組成并行編程的基本原語。對(duì)于一次性的計(jì)算,或是僅僅為了加快現(xiàn)有方法的運(yùn)行速度,選擇輕量化的GCD就更加方便。

小結(jié)

通過NSOperation與NSOperationQueue的結(jié)合使用,我們可以實(shí)現(xiàn)與GCD相同的多線程并發(fā)操作,并且能夠更方便的對(duì)異步任務(wù)進(jìn)行管理控制。

相關(guān)文章

還在用GCD?來看看 NSOperation 吧
NSOperation v.s GCD
iOS多線程編程總結(jié)
iOS多線程(三):NSOperationQueue 的使用

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 原文:http://www.cocoachina.com/ios/20170707/19769.html 本文主要...
    冬的天閱讀 2,409評(píng)論 0 12
  • Object C中創(chuàng)建線程的方法是什么?如果在主線程中執(zhí)行代碼,方法是什么?如果想延時(shí)執(zhí)行代碼、方法又是什么? 1...
    AlanGe閱讀 1,908評(píng)論 0 17
  • 目錄 一、基本概念1.多線程2.串行和并行, 并發(fā)3.隊(duì)列與任務(wù)4.同步與異步5.線程狀態(tài)6.多線程方案 二、GC...
    BohrIsLay閱讀 1,693評(píng)論 5 12
  • 多線程 在iOS開發(fā)中為提高程序的運(yùn)行效率會(huì)將比較耗時(shí)的操作放在子線程中執(zhí)行,iOS系統(tǒng)進(jìn)程默認(rèn)啟動(dòng)一個(gè)主線程,用...
    郭豪豪閱讀 2,719評(píng)論 0 4
  • 如煙伸出兩條胳膊,擺出大大的“大”字,攔住了褚國棟。 褚國棟愣在原地。 如煙一臉的委屈,“答應(yīng)我一件事,現(xiàn)在陪我去...
    愛笑的美人魚閱讀 783評(píng)論 0 1

友情鏈接更多精彩內(nèi)容