【问题标题】:volatile array and multithreaded sorting易失性数组和多线程排序
【发布时间】:2014-07-17 21:16:01
【问题描述】:

我正在考虑使用一个易失数组来实现多线程排序。假设我有一个长度为 N 的数组和 M 个线程,它们将对数组的子范围进行排序。这些子范围是不相交的。然后,在主线程中,我将合并部分排序的数组。

示例代码:

final int N = ....
volatile MyClass[] array = new MyClass[N];
//... fill array with values

void sort(){
    MyThread[] workers = new MyThread[M];
    int len = N/M; //length of the sub-range
    for(int i=0;i<M;++i){
        workers[i] = new MyThread(i*len, (i+1)*len);
        workers[i].start();
    }
    for(int i=0;i<M;++i)workers.join();

    //now synchronization in memory using "happens before"
    //will it work?
    array = array;

    //...merge sorted sub-ranges into one sorted array
}

private class MyThread extends Thread{

final int from;
final int to;

public MyThread(int from, int to){ ..... }

public void run(){
    //...something like: quicksort(array, from, to);
    //...without synchronization, ranges <from, to> are exclusive
}

我不需要在运行线程时在内存中进行同步,因为数组子范围是不相交的。我想在完成线程后进行一次同步。数组的更新版本(在主线程中看到)是否包含在工作线程中所做的所有更改?

如果这个方案有效,对大表有效吗?

提前感谢您的帮助。

编辑:

我进行了测试。无论使用 volatile 关键字,我都收到了正确的结果。但是 volatile 数组的执行时间要长几倍(大约 M 倍)。

【问题讨论】:

  • N 和 M 将有多大?你的目标系统可以本地运行多少线程?线程之间上下文切换的开销很可能会消耗多线程排序阶段的大部分加速。你会留下很多复杂的代码和(也许)小的性能提升。
  • N 大约是 10^6,M 是处理器核心数
  • 在我的机器上用普通的Collections.sort() 对一百万个元素进行排序平均需要大约 400 毫秒的整数,650 毫秒的十六进制字符串。除非MyClass 有一个超级复杂的比较逻辑,否则你会看到几秒钟,比如
  • 为了简单起见,我写了关于排序的文章。工作线程在排序前还要进行其他操作,整个过程反复迭代(我的代码是遗传算法的实现)。

标签: java arrays multithreading sorting volatile


【解决方案1】:

不是答案,只是一些想法:

没有可变数组这样的东西。只有字段可以是可变的。您已经声明了一个名为“array”的 volatile 字段,并使用对数组对象的引用对其进行了初始化。

您似乎希望array = array 语句充当完整的内存屏障。我不知道它是否会或不会,或者答案是否取决于您使用的编译器、JVM 和操作系统。也许有人比我能回答的更专家。

我不喜欢它有两个原因:一个是,它看起来像一个空操作。这是对其他不了解您要做什么的程序员的邀请,并通过删除代码来“清理”代码。像这样一个棘手的语句应该包含在一个函数中,该函数的名称可以解释这个技巧。

二是,该语句的功能与字段引用的数组无关。最好使用 volatile int 字段或 volatile somethingelse 字段,显然 与数组没有任何联系,从而引起人们注意,重要的是字段值以外的其他内容。


更新:根据 Brian Goetz 的说法,该声明不会满足您的要求。您需要的是每个工作线程在完成其工作后更新 volatile 字段,然后您需要主线程在尝试查看工作人员的结果之前读取 volatile 字段。

另一方面……你需要屏障吗?工作线程全部终止并且主线程加入()它们还不够吗?再说一次,也许比我更专业的人可以回答。

【讨论】:

  • 是的,你是对的,表不需要是 volatile,我想我可以使用 volatile 'flag' 字段。我不确定这里是否需要屏障。请阅读我的编辑。
【解决方案2】:

您正在做的事情看起来很混乱,并且按照建议,可能无法按预期工作。

如果您使用 Java8,那么并行排序可能适合您。否则——

对单个数组进行并行排序是一场恐怖表演。如果您创建一个新的排序元素数组,则并行排序相当简单。

创建子数组的对象(您最终需要这样做)。将每个对象传递给一个线程。让线程并行排序它们的对象。完成所有排序后,将排序后的对象合并到一个新数组中。

这意味着需要更多内存,但它相当简单,您无需担心易失性或同步问题。

【讨论】:

  • 感谢您的建议,我考虑将我的数组划分为子数组。那么,我需要挥发性屏障吗?
  • 没有。您没有同时访问任何字段。
  • 感谢您的澄清。
猜你喜欢
  • 1970-01-01
  • 2023-03-21
  • 2017-08-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-07
  • 2011-07-07
  • 1970-01-01
相关资源
最近更新 更多