大规模多线程操作

时间:2012-08-20 11:25:44

标签: multithreading macos cocoa grand-central-dispatch nsoperationqueue

使用以下新代码编辑

我在多线程方面相对较新,但为了实现我的目标,快速完成并学习新东西,我决定使用多线程应用程序。

目标:从文件中解析大量字符串,并使用CoreData将每个单词保存到SQLite数据库中。 很大,因为单词的数量大约是300.000 ......

所以这是我的方法。

步骤1.将所有单词解析到文件中,将其放入一个巨大的NSArray中。 (快点完成)

步骤2.创建插入NSBlockOperation的NSOperationQueue。

主要问题是这个过程很快就开始了,但很快就会减速。我使用NSOperationQueue,最大并发操作设置为100.我有一个Core 2 Duo Process(双核没有HT)。

我看到使用NSOperationQueue会产生很多开销来创建NSOperation(停止调度队列需要大约3分钟来创建300k NSOperation。) 当我开始调度队列时,CPU达到170%。

我尝试删除NSOperationQueue并使用GDC(300k循环是瞬时完成的(注释行))但是使用的cpu只有95%,问题与NSOperations相同。很快这个过程就慢了。

做得好的一些提示?

这里有一些代码(原始问题代码):

- (void)inserdWords:(NSArray *)words insideDictionary:(Dictionary *)dictionary {
    NSDate *creationDate = [NSDate date];

    __block NSUInteger counter = 0;

    NSArray *dictionaryWords = [dictionary.words allObjects];
    NSMutableSet *coreDataWords = [NSMutableSet setWithCapacity:words.count];

    NSLog(@"Begin Adding Operations");

    for (NSString *aWord in words) {

        void(^wordParsingBlock)(void) = ^(void) {
            @synchronized(dictionary) {
                NSManagedObjectContext *context = [(PRDGAppDelegate*)[[NSApplication sharedApplication] delegate] managedObjectContext];                

                [context lock];

                Word *toSaveWord = [NSEntityDescription insertNewObjectForEntityForName:@"Word" inManagedObjectContext:context];
                [toSaveWord setCreated:creationDate];
                [toSaveWord setText:aWord];
                [toSaveWord addDictionariesObject:dictionary];

                [coreDataWords addObject:toSaveWord];
                [dictionary addWordsObject:toSaveWord];

                [context unlock];

                counter++;
                [self.countLabel performSelectorOnMainThread:@selector(setStringValue:) withObject:[NSString stringWithFormat:@"%lu/%lu", counter, words.count] waitUntilDone:NO];

            }
        };

        [_operationsQueue addOperationWithBlock:wordParsingBlock];
//        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//        dispatch_async(queue, wordParsingBlock);
    }
    NSLog(@"Operations Added");
}

提前谢谢。

编辑...

感谢Stephen Darlington,我重写了我的代码,并找出了问题所在。最重要的是:不要在线程之间共享CoreData对象 ...这意味着不要混合使用不同上下文检索的Core数据对象。

这让我使用@synchronized(字典)导致慢动作代码执行! 我只使用MAXTHREAD实例删除了大量的NSOperation创建。 (2或4而不是300k ......是一个巨大的差异)

现在我可以在30/40秒内解析300k + String。令人印象深刻! 我仍然有一些问题(接缝解析更多的单词,而不是只有1个线程,如果线程超过1,它解析不是所有单词......我需要解决它)但现在代码非常有效。也许下一步可能是使用OpenCL并将其注入GPU:)

这里是新代码

- (void)insertWords:(NSArray *)words forLanguage:(NSString *)language {
    NSDate *creationDate = [NSDate date];
    NSPersistentStoreCoordinator *coordinator = [(PRDGAppDelegate*)[[NSApplication sharedApplication] delegate] persistentStoreCoordinator];

    // The number of words to be parsed by the single thread.
    NSUInteger wordsPerThread = (NSUInteger)ceil((double)words.count / (double)MAXTHREADS);

    NSLog(@"Start Adding Operations");
    // Here I minimized the number of threads. Every thread will parse and convert a finite number of words instead of 1 word per thread.
    for (NSUInteger threadIdx = 0; threadIdx < MAXTHREADS; threadIdx++) {

        // The NSBlockOperation.
        void(^threadBlock)(void) = ^(void) {
            // A new Context for the current thread.
            NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
            [context setPersistentStoreCoordinator:coordinator];

            // Dictionary now is in accordance with the thread context.
            Dictionary *dictionary = [PRDGMainController dictionaryForLanguage:language usingContext:context];

            // Stat Variable. Needed to update the UI.
            NSTimeInterval beginInterval = [[NSDate date] timeIntervalSince1970];
            NSUInteger operationPerInterval = 0;

            // The NSOperation Core. It create a CoreDataWord.
            for (NSUInteger wordIdx = 0; wordIdx < wordsPerThread && wordsPerThread * threadIdx + wordIdx < words.count; wordIdx++) {
                // The String to convert
                NSString *aWord = [words objectAtIndex:wordsPerThread * threadIdx + wordIdx];

                // Some Exceptions to skip certain words.
                if (...) {
                    continue;
                }

                // CoreData Conversion.
                Word *toSaveWord = [NSEntityDescription insertNewObjectForEntityForName:@"Word" inManagedObjectContext:context];
                [toSaveWord setCreated:creationDate];
                [toSaveWord setText:aWord];
                [toSaveWord addDictionariesObject:dictionary];

                operationPerInterval++;

                NSTimeInterval endInterval = [[NSDate date] timeIntervalSince1970];

                // Update case.
                if (endInterval - beginInterval > UPDATE_INTERVAL) {

                    NSLog(@"Thread %lu Processed %lu words", threadIdx, wordIdx);

                    // UI Update. It will be updated only by the first queue.
                    if (threadIdx == 0) {

                        // UI Update code.
                    }
                    beginInterval = endInterval;
                    operationPerInterval = 0;
                }
            }

            // When the NSOperation goes to finish the CoreData thread context is saved.
            [context save:nil];
            NSLog(@"Operation %lu finished", threadIdx);
        };

        // Add the NSBlockOperation to queue.
        [_operationsQueue addOperationWithBlock:threadBlock];
    }
    NSLog(@"Operations Added");
}

2 个答案:

答案 0 :(得分:2)

一些想法:

  • 设置如此高的最大并发操作不会产生太大影响。如果你有两个核心,它不可能超过两个
  • 您似乎对所有进程使用相同的NSManagedObjectContext。这不好
  • 让我们假设您的最大并发操作 100.瓶颈将是主线程,您尝试为每个操作更新标签。尝试更新每个 n 记录的主线程,而不是每个
  • 如果您正确使用Core Data,则不需要锁定上下文...这意味着为每个线程使用不同的上下文
  • 您似乎没有保存上下文?
  • 批处理操作是提高性能的好方法......但请参阅上一点
  • 如您所知,创建GCD操作会产生开销。为每个单词创建一个新单词可能不是最佳的。您需要平衡创建新流程的开销和并行化的好处

简而言之,即使你使用像GCD这样的东西,线程也很难。

答案 1 :(得分:0)

如果没有测量和分析,这很难实现,但对我来说可疑的是你保存到目前为止保存的每个单词的完整单词词典。因此,每次保存的数据量会越来越大。

// the dictionary at this point contains all words saved so far
// which each contains a full dictionary
[toSaveWord addDictionariesObject:dictionary];

// add each time so it gets bigger each time
[dictionary addWordsObject:toSaveWord];

因此,每次保存都会节省越来越多的数据。为什么用每个单词保存所有单词的字典?

其他一些想法:

  • 为什么要构建你从未使用过的coreDataWords?
  • 我想知道你是否正在获得并发性,因为你正在同步整个工作块。

要尝试的事情:

    除了你正在构建的字典之外,
  • 注释掉toSaveWord上的字典,然后再试一次 - 看看它是你的数据/数据结构还是DB / coreData。
  • 做第一个,但也创建它的序列版本,看看你是否真正获得了concurency的好处。