【问题标题】:GCD serial queue and race conditionGCD 串行队列和竞争条件
【发布时间】:2015-08-06 06:18:10
【问题描述】:

我有两种在串行队列上运行的方法。每个方法都返回某个类的副本。我正在尝试实现线程安全解决方案,同时保持数据完整性。

例如:

-(Users *) getAllUsers
{ 
       __block copiedUsers;
       dispatch_sync(_backgroundQueue, ^{
            copiedUsers = [self.users copy]; // return copy object to calling thread.  
       });
       return copiedUsers;
}

-(Orders *) getAllOrders
{ 
       __block copiedOrders;
       dispatch_sync(_backgroundQueue, ^{
            copiedOrders = [self.Orders copy]; // return copy object to calling thread.
       });
       return copiedOrders;
}

除了这两种方法之外,我还有一个添加/删除用户和订单的工作类,所有这些都通过串行队列backgroundQueue 完成。

如果在主线程中我调用getAllUsers,然后在另一个之后调用getAllOrders,我的数据完整性是不安全的,因为在这两个调用之间工作类可能已经改变了模型。

我的问题是如何为调用者提供一个允许多个方法以原子方式运行的良好接口?

【问题讨论】:

    标签: ios objective-c iphone multithreading


    【解决方案1】:

    模型仅从backgroundQueue 串行队列更新。 客户端通过接收在后台队列中运行的块的方法与模型对话。

    另外,为了不冻结主线程,我创建了另一个队列并运行了一个与网关方法对话的块。

    P.S - 注意 dispatch_sync 仅在 runBlockAndGetNeededDataSafely 中调用以避免死锁。

    代码示例:

    aViewController.m

    ManagerClass *m = [ManagerClass new];    
    dispatch_queue_t q = dispatch_queue_create("funnelQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_block_t block_q = ^{
        __Users *users;
        __Orders *orders;
        [manager runBlockAndGetNeededDataSafely:^
         {
                users  = [manager getUsers];
                orders = [manager getOrders];
               dispatch_async(dispatch_get_main_queue(),
              ^{
                   // got data safely - no thread issues, copied objects. update UI!
                   [self refreshViewWithUsers:users 
                                      orders:orders];
               });
         }];
    }
    dispatch_async(q, block_q);
    

    Manager.m 实现:

    -(void) runBlockInBackground:(dispatch_block_t) block
    {
        dispatch_sync(self.backgroundQueue, block); 
    }
     -(Users *) getAllUsers
     { 
           return [self.users copy];
     }
    
     -(Orders *) getAllOrders
     {  
           return [self.Orders copy];
     }
    

    【讨论】:

    • 我已编辑您的答案以便于讨论。 block_q 中的代码将task 排队到backgroundQueue,然后通知主线程。您将在ordersusers 更新之前获得更新信号。我认为如果您在block_q 中有一个dispatch_semaphore_t 并等到runBlockAndGetNeededDataSafely 完成,您的代码将正常工作。
    • 感谢编辑,我批准了。你的意思是什么更新信号?为什么我需要 dispatch_semaphore_t?
    • 更新信号为refreshViewWithUsers。因为无论runBlockAndGetNeededDataSafely完成与否,它都会执行refreshViewWithUsers
    • 更新答案,有一个小错误,请查看更新答案。
    • 如果你这么说,你的代码会很好。我也被你解决问题的方式所启发。很好的答案。
    【解决方案2】:

    要回答有关如何检查当前队列的问题: 首先,当你创建队列时,给它一个标签:

    static void* queueTag = &queueTag;
    dispatch_queue_t queue = dispatch_queue_create("a queue", 0);
    dispatch_queue_set_specific(queue, queueTag, queueTag, NULL);
    

    然后像这样运行一个块:

    -(void)runBlock:(void(^)()) block
    {
        if (dispatch_get_specific(queueTag) != NULL) {
            block();
        }else {
            dispatch_async(self.queue, block);
        }
    }
    

    【讨论】:

    • 整洁。你确定使用这个没有问题吗?因为这确实是防止死锁的好方法:-)
    • 太好了。我也将开始使用它,这可能非常有用。谢谢!
    【解决方案3】:

    您的示例不起作用。我建议使用完成回调。您应该可以选择知道工人何时完成工作以恢复价值。

    - (void)waitForCompletion:(BOOL*)conditions length:(int)len timeOut:(NSInteger)timeoutSecs {
        NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:timeoutSecs];
        BOOL done = YES;
        for (int i = 0; i < len; i++) {
            done = done & *(conditions+i);
        }
        do {
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:timeoutDate];
            if([timeoutDate timeIntervalSinceNow] < 0.0)
                break;
    
            //update done
            done = YES;
            for (int i = 0; i < len; i++) {
                done = done & *(conditions+i);
            }
        } while (!done);
    }
    
    
    -(void) getAllUsers:(void(^)(User* user, NSError* error))completion
    {
        dispatch_async(_backgroundQueue, ^{
            BOOL condition[2] = [self.userCondition, self.orderCondition];
            [self waitForCompletion: &condition[0] length:2 timeOut:60];
            if (completion) {
                completion([self.users copy], nil);
            }
        });
    }
    

    【讨论】:

    • 谢谢,用正确的实现更新了问题。
    • 您的示例将立即返回一个指针并稍后更改其值。当更改发生时您不会这样做,因此如果您需要更新 UI 会很困难。
    • 是的,get 方法最好同步运行,因为我在我的问题中进行了更新。
    • 好的。然后请注意,您可以冻结主线程。而且,如果你在同一个_backgroundQueue中调用这个方法,可能会发生死锁。
    • sahara108,完全了解这一点,感谢您的警告。我什至会更新问题以处理死锁。 :-)
    猜你喜欢
    • 2019-07-04
    • 1970-01-01
    • 2010-10-30
    • 1970-01-01
    • 2013-01-14
    • 1970-01-01
    • 2020-07-12
    • 2011-01-08
    • 1970-01-01
    相关资源
    最近更新 更多