如何将代码块分派到iOS中的同一个线程?

Tim*_*olz 17 sqlite asynchronous thread-safety grand-central-dispatch ios

问题的主要方面:它是关于iOS的.我可以以某种方式调度代码块,他们将(a)在后台运行和(b)在同一个线程上运行吗?我想在后台运行一些耗时的操作,但这些操作必须在同一个线程上运行,因为它们涉及资源,不能在线程之间共享.

进一步的技术细节,如果需要:它是关于实现Apache Cordova的sqlite插件,Apache Cordova是移动平台上HTML5应用程序的框架.这个插件应该是Cordova的插件API 的WebSQL实现.(这意味着,不可能将整个事务包装在单个块中,这可以使一切变得更容易.)

以下是Cordova文档中的一些代码:

- (void)myPluginMethod:(CDVInvokedUrlCommand*)command
{
    // Check command.arguments here.
    [self.commandDelegate runInBackground:^{
        NSString* payload = nil;
        // Some blocking logic...
        CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:payload];
        // The sendPluginResult method is thread-safe.
        [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
    }];
}
Run Code Online (Sandbox Code Playgroud)

但据我所知,无法保证那些调度的代码块(请参阅runInBackground)将在同一个线程上运行.

Dar*_*ust 21

GCD不保证两个块在同一个线程上运行,即使它们属于同一个队列(当然也不包括主队列).但是,如果您正在使用串行队列(DISPATCH_QUEUE_SERIAL),那么这不是问题,因为您知道没有对数据的并发访问.

手册页dispatch_queue_create说:

队列不绑定到任何特定的执行线程,并且提交给独立队列的块可以并发执行.

我不知道将队列绑定到特定线程的任何方法(毕竟,不需要关心线程是GCD的一个重点).您可以使用串行队列而不必担心实际线程的原因是这个承诺:

由分派到串行队列的块执行的所有内存写入保证对分派到同一队列的后续块可见.

也就是说,似乎使用了内存屏障.

在处理线程问题时,您主要关心的是避免两个线程同时访问某些内容.如果您使用的是串行队列,则不会出现此问题.通常哪个线程正在访问您的资源并不重要.例如,我们使用串行队列来管理Core Data访问而没有任何问题.

编辑:

看起来你真的发现了一个罕见的情况,你需要在同一个线程上工作.您可以实现自己的工作线程:

  • 先决条件:
    • 一个NSMutableArray(我们称之为blockQueue).
    • 一个NSCondition(我们称之为queueCondition).
  • 创建一个新的NSThread.
    • 线程的方法有一个无限循环,它锁定条件,如果队列为空(等待"退出"bool为假)则等待它,使一个块出列并执行它.
  • 锁定条件的方法将块排队.

由于条件,线程将只是睡觉,而没有工作要做.

所以,大致(未经测试,假设ARC):

- (void)startWorkerThread
{
    workerThread = [[NSThread alloc]
        initWithTarget:self
        selector:@selector(threadMain)
        object:nil
    ];
    [workerThread start];
}

- (void)threadMain
{
    void (^block)();
    NSThread *currentThread;

    currentThread = [NSThread currentThread];

    while (1) {
        [queueCondition lock];
        {
            while ([blockQueue count] == 0 && ![currentThread isCancelled]) {
                [queueCondition wait];
            }

            if ([currentThread isCancelled]) {
                [queueCondition unlock];
                return;
            }

            block = [blockQueue objectAtIndex:0];
            [blockQueue removeObjectAtIndex:0];
        }
        [queueCondition unlock];

        // Execute block outside the condition, since it's also a lock!
        // We want to give other threads the possibility to enqueue
        // a new block while we're executing a block.
        block();
    }
}

- (void)enqueue:(void(^)())block
{
    [queueCondition lock];
    {
        // Copy the block! IIRC you'll get strange things or
        // even crashes if you don't.
        [blockQueue addObject:[block copy]];
        [queueCondition signal];
    }
    [queueCondition unlock];
}

- (void)stopThread
{
    [queueCondition lock];
    {
        [workerThread cancel];
        [queueCondition signal];
    }
    [queueCondition unlock];
}
Run Code Online (Sandbox Code Playgroud)