为什么weak_table_t是Objective-C运行时中SideTable的成员



我正在从 https://github.com/opensource-apple/objc4 读取objc的代码。

在代码中,有一个结构 SideTable,其中包含相应对象的引用计数和一个weak_table_t。

struct SideTable {
    spinlock_t slock;
    RefcountMap refcnts;
    weak_table_t weak_table;
    SideTable() {
        memset(&weak_table, 0, sizeof(weak_table));
    }
    ~SideTable() {
        _objc_fatal("Do not delete SideTable.");
    }
    void lock() { slock.lock(); }
    void unlock() { slock.unlock(); }
    bool trylock() { return slock.trylock(); }
    // Address-ordered lock discipline for a pair of side tables.
    template<bool HaveOld, bool HaveNew>
    static void lockTwo(SideTable *lock1, SideTable *lock2);
    template<bool HaveOld, bool HaveNew>
    static void unlockTwo(SideTable *lock1, SideTable *lock2);
};
对象的 SideTable

可以被 SideTables(([obj] 检索,因为每个对象的 SideTable 都存储在 StripedMap 中,而 StripedMap 实际上是一个使用对象地址的哈希值作为索引的数组。

但是根据weak_entry_for_referent的代码,运行时通过检查weak_table->weak_entries[index].referent来获取引用的weak_entry_t。

static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
    assert(referent);
    weak_entry_t *weak_entries = weak_table->weak_entries;
    if (!weak_entries) return nil;
    size_t index = hash_pointer(referent) & weak_table->mask;
    size_t hash_displacement = 0;
    while (weak_table->weak_entries[index].referent != referent) {
        index = (index+1) & weak_table->mask;
        hash_displacement++;
        if (hash_displacement > weak_table->max_hash_displacement) {
            return nil;
        }
    }
    return &weak_table->weak_entries[index];
}

这意味着weak_table包含的不仅仅是单个对象的弱条目。那为什么weak_table_t是SideTable的成员而不是全局数据呢?

由于我找不到真正初始化对象的 SideTable 的代码(storeStrong 一开始只使用 SideTable 而不初始化它(和weak_table,我不太明白事情在后台是如何工作的。

谁能给我一个提示?

为什么weak_table_t是SideTable的成员

如果使用全局weak_table_t,应解决两种情况。(refcnts有相同的情况(

  • 案例 1:维护weak_table_t应该是线程安全的,所以我们需要锁定
  • 案例2:锁定意味着慢,但开发人员希望系统尽可能快地运行

只有一个全局weak_table_t无法解决上述两种情况。

实际上,class StripedMap通过使用lock striping来解决这两种情况,方法是将weak_table_trefcnts一起包装在SideTable中。

让我们看一下下面的代码,从objc-private.h复制而来。

// StripedMap<T> is a map of void* -> T, sized appropriately 
// for cache-friendly lock striping. 
// For example, this may be used as StripedMap<spinlock_t>
// or as StripedMap<SomeStruct> where SomeStruct stores a spin lock.
template<typename T>
class StripedMap {
    // ... code for StripedMap
    PaddedT array[StripeCount]; // There are 8 SideTables if define TARGET_OS_IPHONE
    // ... code for StripedMap
}
8

边桌存储 8 weak_table_t和 8 spinlock_t slock .系统最多可以保持8个weak_table_t并发8个线程。我认为这在iOS设备上已经足够快了。

边表初始化

SideTablesMap.init();arr_init()打电话来的.您可以在class ExplicitInit init上使用"查找呼叫层次结构"来查找呼叫层次结构

我能弄清楚的唯一合理原因是:

所有 SideTable 实例的 SideTableBuf 实际上是一个存储桶数组。不同的对象可以放在同一个存储桶中。因此,单个 SideTable 可以根据哈希算法为不同的指针提供服务。然后,weak_table实际上包含不同指代物的weak_entry_t。这就是为什么我们需要通过以下方式检查引用:

while (weak_table->weak_entries[index].referent != referent)

并且weak_table仅适用于当前的 SideTable,因此它不能是全局数据。

表是一个共享资源,允许从多个线程进行访问。因此,我们需要一个锁来避免数据不一致。所以weak_table_tspinlock_t一起被包裹在SideTable中.

但是,为每次访问锁定整个表是低效的。考虑到这种情况,线程 A 已获取锁,并且正在修改弱表。同时,线程 B 想要修改弱表,因此它尝试获取锁。但是,一次只有一个线程可以拥有锁。所以线程 B 块。 如果这种情况一直发生,对弱表的访问将非常慢。

然后开发人员提出了"锁定条带化"模式,将表划分为多个存储桶,每个存储桶都可以独立锁定。在此之后,修改存储桶 1 不会阻止修改存储桶 2。因此,出于分区目的,SideTable被包裹在StripedMap中。

最新更新