C中的原子递减和测试

时间:2016-06-18 17:08:10

标签: c atomic c11 stdatomic

我在C中实现了一个需要使用多个线程的引用计数系统。因此,我需要一种方法来减少整数引用计数,并测试一次原子操作结果是否为零。我可以使用C11和stdatomic.h,但似乎没有减量和测试操作。

最好的(即最便携的)方式是什么?我可以使用AutoField函数来实现此目的吗?

这是引用计数(伪代码)的核心:

id = models.AutoField(primary_key=True)

2 个答案:

答案 0 :(得分:5)

你似乎误解了C11的原子。 Atomic限定类型,而不是单个操作。

如果用_Atomic声明变量,则对它的所有操作都是原子的。因此,如果您对原子操作的默认“顺序一致性”(您应该)感到满意,那么您需要额外的_Atomic限定条件。前缀--运算符应该可以满足您的需求。

如果您想要处理不同类型的一致性,可以使用atomic_fetch_sub,例如只有那时你才能获得修改前的值,而不是之后的值。因此,您应该将其与0进行比较,而不是与1进行比较。

答案 1 :(得分:0)

很抱歉在游行中下雨,但无论使用哪种原子增量/减量原语,这都可以使用上述机制完成。

release执行free的瞬间,对象变为无效[我们必须假设另一个线程立即执行malloc并重新利用内存]并且可以通过任何线程进一步访问它。

free之后,不能为该对象调用retainrelease。甚至不仅仅是探测ref_count值。简单的ref_count inc / dec [原子与否]不足以处理/防止这种情况。

(1)interthread锁必须位于对象对象之外,并且不得有任何alloc / free。

(2)必须通过某种列表遍历来访问对象。也就是说,有一个活动对象列表。

(3)对列表的访问由互斥锁控制。因此,实际的inc / dec [可能] 需要是原子的[但可能是为了额外的安全]

(4)使用该列表可确保一旦对象被销毁,任何线程都不会尝试访问它,因为它已从活动对象列表中删除,并且线程不再是"参见"它

retainrelease必须执行以下操作:

int
retain(List *list,Object *object)
{
    int match = 0;

    lock_list(list);

    for (objnow in list) {
        if (objnow is object) {
            ++objnow.ref_count;
            match = 1;
            break;
        }
    }

    unlock_list(list);

    return match;
}

int
release(List *list,Object *object)
{
    int match = 0;

    lock_list(list);

    for (objnow in list) {
        if (objnow is object) {
            match = 1;
            if (--objnow.ref_count == 0) {
                unlink_from_list(list,objnow);
                free(objnow);
                match = -1;
                break;
            }
        }
    }

    unlock_list(list);

    return match;
}

列表上方的互斥/锁定方法也可以使用RCU完成,但这有点复杂。

当然," list"这里不需要是一个简单的链表。它可以是B树或其他类型的容器。

一个概念:实际上,在考虑它时,如果一个对象附加到某种全局/线程列表,ref_count倾向于失去意义。或者更重要的是,为什么会在ref_count上发生线上争用?

如果我们只是有一些"浮动"在列表上的对象[或在本地每线程列表上],为什么多个线程会尝试向上/向下ref_count,因为它更可能单个线程将拥有"那时的对象。

否则,重新架构系统可能是为了使其更具可预测性/稳定性。

<强>更新

  

线程可能不会碰撞引用计数,除非它已经有引用,因为访问该对象需要引用。

通过引用,在这里,我假设你的意思是线程已经完成retain,会做一些事情,然后做release

  

因此,如果引用计数达到零,则当前没有线程正在访问该对象,也不会有任何线程将来这样做。因此,摧毁它是安全的。

销毁它可能是安全的,但是对于访问对象内的数据[非锁定]单元并且发生碰撞的多个线程没有互锁。

问题是让子线程执行free

考虑到我们有一个主线程,它创建了一个对象obj1,并将其传递给两个线程tAtB,它们在内部称为{{1}分别和objA

主线程启动objB,引用计数为零。

考虑以下时间表:

obj1

对象引用计数现在为零,并且已释放内存区域。任何进一步的访问都无效。 tA: retain(objA) tA: // do stuff ... tA: release(objA) 可能无法以任何方式访问tB的内存区域。

现在,我们[如果我们选择忽略]:

obj1

tB: retain(objB) tB: // do stuff ... tB: release(objB) 的发布会看到引用计数变为零并执行tB。这是free

double free

但是,obj1甚至无法执行tB,因为retain的内存可能已被另一个线程重新分配:(1){{1}的主线程1}}或(2)另一个线程obj1,它将内存用于完全不相关的目的

在(1)的情况下,obj2&#39; tX现在正在更改tB而不是objB

在(2)的情况下,obj2obj1的无关内存区域上涂鸦。即使是短暂的inc / dec也是灾难性的。

因此,在上面,有竞争条件,访问已释放的内存,双重释放,以及写入(例如)objB,就像它是tX一样。

那么,如果我们让主线程用refcount初始化为1而不是零呢?

现在,事情变得更好了。比赛条件被消除。但是,objtype_xobjtype_a 永远不会看到引用数量低于1,因此 中的都不会执行tA。因此,让单个线程执行tB是一个没有实际意义的点。

主线程必须执行free,这是安全的。但是,main无法知道状态free是什么状态。也就是说,它是由freeobj1还是两者处理的?

所以,也许对象需要一个tA掩码,与tBdone一起[原子地]进行“原子化”,主要会看这个,知道它什么时候可以1 << tA

或者,主线程,如果它知道只有两个线程1 << tBfree将访问该对象,它可以将refcount初始化为两个,并且两个线程可以只执行{{ 1}}当他们完成对象时。

如果tA决定在完成自己的处理后,需要将对象发送到tB,这样做效果不会很好。

只需引用引用,如果在 release之前必须由tB 处理给定对象,则无法确保这一点。

从架构上讲,如果每个线程都有一个输入队列/列表[互斥锁定],整个系统可能会更好。主线程创建一个对象,将其排队到tCtA将其排队,确实有效,并将其排入tB。每个帖子都可以做一个&#34; Y&#34;叉子。也就是说,tA查看对象并决定将其发送到tA,完全绕过tB。最终,其中一个线程将对象排队回主线程(即用过的对象的空闲列表或将结果发布到main(例如map / reduce的形式))。

将一个对象放在一个[可重用的]免费列表上(与执行tA相比)可以减轻一些因素,因为我们没有&#34; rug pull&#34;执行tC [与立即tB]的效果,因此我们可以将一些状态信息存储在对象中,即使对象是空闲&#34;。

因此,我们有一个intethread管道系统的效果。

这种方法的优点之一[我在运输生产系统中成功使用]的一个优点是,一旦一个对象排队到一个线程,该线程就会拥有&#34;对象和大多数访问都不需要是原子的。