【发布时间】:2011-05-14 15:14:57
【问题描述】:
我正在尝试编写一个线程安全的排序单链表。我写了两个版本:粗粒度同步和细粒度同步。以下是两个实现:
细粒度:
public void add(T t) {
Node curr = head;
curr.lock.lock();
while (curr.next != null) {
// Invariant: curr is locked
// Invariant: curr.data < t
curr.next.lock.lock();
if (t.compareTo(curr.next.data) <= 0) {
break;
}
Node tmp = curr.next;
curr.lock.unlock();
curr = tmp;
}
// curr is acquired
curr.next = new Node(curr.next, t);
if (curr.next.next != null) { // old curr's next is acquired
curr.next.next.lock.unlock();
}
curr.lock.unlock();
}
粗粒度:
public void add(T t) {
lock.lock();
Node curr = head;
while (curr.next != null) {
if (t.compareTo(curr.next.data) <= 0) {
break;
}
curr = curr.next;
}
curr.next = new Node(curr.next, t);
lock.unlock();
}
我在插入 20000 个整数的 4 个线程(在 4 个逻辑 CPU 内核上)对两个版本进行了计时。每个线程的时间显示 CPU 时间(即不包括等待时间)。
Fine grained:
Worked 1 spent 1080 ms
Worked 2 spent 1230 ms
Worked 0 spent 1250 ms
Worked 3 spent 1260 ms
wall time: 1620 ms
Coarse grained:
Worked 1 spent 190 ms
Worked 2 spent 270 ms
Worked 3 spent 410 ms
Worked 0 spent 280 ms
wall time: 1298 ms
我最初的想法是 .lock() 和 .unlock() 是问题所在,但我分析了实现,它们加起来只消耗了 30% 的时间。我的第二个猜测是细粒度解决方案有更多的缓存未命中,但我对此表示怀疑,因为与数组不同,单个链表天生就容易出现缓存未命中。
知道为什么我没有得到预期的并行化吗?
【问题讨论】:
-
简要看一下,我会说细粒度解决方案对每个插入执行 O(n) 锁定,粗粒度解决方案仅 O(1)。只有 20000 个整数,锁定开销似乎占主导地位。
-
我也想过。我增加到 100_000 个元素,结果相似。锁定/解锁只需要 30% 的时间(这是 jprofiler 告诉我的),并且没有考虑时间差。
-
30% 整体还是每个线程?
-
Sorted 链表是一个糟糕的主意,如果你有任何大量的修改。如果有树,你会更好(好多了)。
标签: java multithreading linked-list