2

无论 SO 和其他网站上的其他问题问“我如何编写并发代码? ”,答案总是涉及非常模糊的解释,例如“检查数据依赖关系”或代码内部的“相互依赖关系”。我想知道这些神秘的依赖项实际上是什么样的 Java 代码!?!

  • 一段代码的具体示例可以很容易地并行化,因为它的一部分不依赖于另一部分?
  • 由于存在这些依赖关系,一段代码必须是串行的具体示例是什么?
  • 这些依赖关系的存在如何影响是否使用线程池的决定?

我想我只是没有在这里看到“树林中的森林”。提前致谢!

4

4 回答 4

3

下面是一些简单的例子:

1.无依赖,易于并行化:

 int[] array = new int[size];
 for(int i = 0; i < size; i++) {
     array[i] = array[i] * array[i];
 }

每个项目都是独立计算的。

2.对上一次迭代的依赖:

 int[] array = new int[size];
 for(int i = 1; i < size; i++) {
     array[i] = array[i - 1] * 2 + array[i];
 }

array[3]除非你有,否则你无法计算array[2](也许在某些情况下,你可以推导出一个允许重组的公式,但你明白了)。

线程池的使用与依赖关系的存在或不存在真正相关。这些概念是正交的。线程池只是回收线程的一种有效方式,线程通常是系统中昂贵的资源。

于 2012-04-28T17:00:45.603 回答
1

假设您想编写一个银行账户对象并且您想维护一个不变量——货币永远不会由于对银行账户的操作而神奇地创建或销毁。

你可能会写

int balance;

void deposit(int amount) {
  if (amount < 0) { throw ... }
  long after = balance + amount;
  if (after > Integer.MAX_VALUE) { throw ... }
  // DANGER 1
  balance = (int) after;
}

void withdraw(int amount) {
  if (amount < 0 || amount > balance) { throw ... }
  // DANGER 2
  balance -= amount;
}

考虑当一个线程在任一 DANGER 评论处暂停并同时发生另一次存款或取款时会发生什么。

如果一笔存款开始并在危险 1 处暂停,然后发生另一笔存款,然后第一笔存款结束,则存款可能会丢失,从而破坏价值。

如果一次提款开始,在 DANGER 2 处暂停,然后发生存款,然后提款结束,则提款可能会使余额变为负数。

如果存款开始,在 DANGER 1 处暂停,然后发生提款,然后存款结束,则可以创建货币。


解决这个问题的标准方法是说,计算after值和分配的代码balance = (int) after是一个关键部分,并且必须在没有对其任何依赖项进行任何干预更改的情况下发生:balance.

类似地,安全检查withdrawal-=操作形成一个关键部分——-=如果检查仍然不正确,则操作不应发生。

为了获得额外的乐趣,请考虑可能发生的事情,因为a -= b实际上有几个声明:

int x = b;
int y = a;
a = y - x;

这些都必须一起发生才能有意义,因此a -= b具有标准含义取决于它的依赖关系a,而b不是在操作中间发生变化。

于 2012-04-28T17:01:38.013 回答
1

一段代码的具体示例可以很容易地并行化,因为它的一部分不依赖于另一部分?

并发应用程序建立在tasks. 您可以将 a 标识task为任何离散的工作单元。如果您可以识别出作为离散工作单元并具有明确任务边界Runnable的代码部分,您可以为每个任务创建s 并以单独的方式运行它以Thread实现并行性。实际上 aRunnable代表Task抽象。
一个示例是需要加载多个文件进行处理的进程。文件的加载是一个独立的工作单元,可以由线程在后台完成,不会阻塞代码流

由于存在这些依赖关系,一段代码必须是串行的具体示例是什么?

如果Task 2依赖于Task 1then的计算/处理Task 2必须等待完成Task 1,从而序列化序列。
一个例子是在后台加载一个文件,然后在文件中搜索一个键

这些依赖关系的存在如何影响是否使用线程池的决定?

他们没有。创建 aThread是一项昂贵的操作,并且Thread Pools是重用线程的构造设计,以避免创建新线程的开销。在 中,您Thread Pool只需传递s 并负责将 分配给线程或在需要时创建新线程。您还可以通过线程池等定义策略。但它们是并发程序的工具。您应该已经确定了要提交到线程池执行的任务。TasksRunnableThread PoolTask

于 2012-04-28T18:06:39.857 回答
0

一个没有依赖关系的简单示例是遍历集合的任何循环,对每个项目执行相同的工作(映射操作,在 FP 中)。确实存在依赖关系的一个典型示例是具有大量 db 提取的业务逻辑,基于它们的决策,然后进一步提取,所有这些都在嵌套的 ifs 中——大量数据处理,但每一步都取决于上一步的结果。

于 2012-04-28T17:01:57.190 回答