[英]Why is this GoLang solution faster then the equivalent Java Solution?
[英]Equivalent of Go channel in Java
我有一個要求,我需要從一組阻塞隊列中讀取。 阻塞隊列是由我正在使用的庫創建的。 我的代碼必須從隊列中讀取。 我不想為每個阻塞隊列創建一個讀取器線程。 相反,我想使用單個線程(或者可能最多使用 2/3 個線程)來輪詢它們的數據可用性。 由於一些阻塞隊列可能長時間沒有數據,而其中一些可能會突發數據。 輪詢具有小超時的隊列會起作用,但這根本沒有效率,因為它仍然需要在所有隊列上循環,即使其中一些隊列長時間沒有數據。 基本上,我正在尋找一種阻塞隊列的選擇/epoll(用於套接字)機制。 非常感謝任何線索。
不過,在 Go 中這樣做確實很容易。 下面的代碼模擬了通道和 goroutines:
package main
import "fmt"
import "time"
import "math/rand"
func sendMessage(sc chan string) {
var i int
for {
i = rand.Intn(10)
for ; i >= 0 ; i-- {
sc <- fmt.Sprintf("Order number %d",rand.Intn(100))
}
i = 1000 + rand.Intn(32000);
time.Sleep(time.Duration(i) * time.Millisecond)
}
}
func sendNum(c chan int) {
var i int
for {
i = rand.Intn(16);
for ; i >= 0; i-- {
time.Sleep(20 * time.Millisecond)
c <- rand.Intn(65534)
}
i = 1000 + rand.Intn(24000);
time.Sleep(time.Duration(i) * time.Millisecond)
}
}
func main() {
msgchan := make(chan string, 32)
numchan := make(chan int, 32)
i := 0
for ; i < 8 ; i++ {
go sendNum(numchan)
go sendMessage(msgchan)
}
for {
select {
case msg := <- msgchan:
fmt.Printf("Worked on %s\n", msg)
case x := <- numchan:
fmt.Printf("I got %d \n", x)
}
}
}
我建議你研究一下使用JCSP庫。 相當於Go的select
被稱為Alternative 。 您只需要一個消耗線程,如果使用Alternative
打開它們,則不需要輪詢傳入通道。 因此,這將是多路復用源數據的有效方式。
如果您能夠使用JCSP頻道替換BlockingQueues,它將會有很大幫助。 信道的行為基本相同,但在信道端的共享扇出或扇入方面提供了更大程度的靈活性,特別是使用Alternative
信道的信道。
對於使用示例,這里是公平的多路復用器。 此示例演示了一個將來自其輸入通道陣列的流量與其單個輸出通道進行相當多路復用的過程。 無論競爭對手的熱情如何,都不會缺乏輸入渠道。
import org.jcsp.lang.*;
public class FairPlex implements CSProcess {
private final AltingChannelInput[] in;
private final ChannelOutput out;
public FairPlex (final AltingChannelInput[] in, final ChannelOutput out) {
this.in = in;
this.out = out;
}
public void run () {
final Alternative alt = new Alternative (in);
while (true) {
final int index = alt.fairSelect ();
out.write (in[index].read ());
}
}
}
請注意,如果priSelect
使用priSelect
,如果索引較低的頻道不斷要求服務,則較高索引的頻道將會缺乏。 或者代替fairSelect
,可以使用select
,但是不能進行飢餓分析。 只有在飢餓不成問題時才應使用select
機制。
擺脫僵局
與Go一樣,使用通道的Java程序必須設計為不會死鎖。 在Java中實現低級並發原語很難做到正確,你需要一些可靠的東西。 幸運的是, Alternative
已經通過形式分析以及JCSP渠道得到驗證。 這使它成為可靠的可靠選擇。
為了澄清一點點困惑,目前的JCSP版本在Maven回購中是1.1-rc5 ,而不是網站所說的。
唯一的方法是用更多功能類的對象替換標准隊列,該類在將數據插入空隊列時通知消費者。 這個類仍然可以實現BlockingQueue接口,所以另一方(生產者)看不出區別。 訣竅是put
操作也應該引發一個標志並通知消費者。 消費者在輪詢所有線程后,清除該標志並調用Object.wait()
。
另一個選擇是Java6 +
BlockingDeque實現類:
import java.lang.ref.WeakReference;
import java.util.WeakHashMap;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.atomic.AtomicLong;
class GoChannelPool {
private final static GoChannelPool defaultInstance = newPool();
private final AtomicLong serialNumber = new AtomicLong();
private final WeakHashMap<Long, WeakReference<GoChannel>> channelWeakHashMap = new WeakHashMap<>();
private final LinkedBlockingDeque<GoChannelObject> totalQueue = new LinkedBlockingDeque<>();
public <T> GoChannel<T> newChannel() {
GoChannel<T> channel = new GoChannel<>();
channelWeakHashMap.put(channel.getId(), new WeakReference<GoChannel>(channel));
return channel;
}
public void select(GoSelectConsumer consumer) throws InterruptedException {
consumer.accept(getTotalQueue().take());
}
public int size() {
return getTotalQueue().size();
}
public int getChannelCount() {
return channelWeakHashMap.values().size();
}
private LinkedBlockingDeque<GoChannelObject> getTotalQueue() {
return totalQueue;
}
public static GoChannelPool getDefaultInstance() {
return defaultInstance;
}
public static GoChannelPool newPool() {
return new GoChannelPool();
}
private GoChannelPool() {}
private long getSerialNumber() {
return serialNumber.getAndIncrement();
}
private synchronized void syncTakeAndDispatchObject() throws InterruptedException {
select(new GoSelectConsumer() {
@Override
void accept(GoChannelObject t) {
WeakReference<GoChannel> goChannelWeakReference = channelWeakHashMap.get(t.channel_id);
GoChannel channel = goChannelWeakReference != null ? goChannelWeakReference.get() : null;
if (channel != null) {
channel.offerBuffer(t);
}
}
});
}
class GoChannel<E> {
// Instance
private final long id;
private final LinkedBlockingDeque<GoChannelObject<E>> buffer = new LinkedBlockingDeque<>();
public GoChannel() {
this(getSerialNumber());
}
private GoChannel(long id) {
this.id = id;
}
public long getId() {
return id;
}
public E take() throws InterruptedException {
GoChannelObject object;
while((object = pollBuffer()) == null) {
syncTakeAndDispatchObject();
}
return (E) object.data;
}
public void offer(E object) {
GoChannelObject<E> e = new GoChannelObject();
e.channel_id = getId();
e.data = object;
getTotalQueue().offer(e);
}
protected void offerBuffer(GoChannelObject<E> data) {
buffer.offer(data);
}
protected GoChannelObject<E> pollBuffer() {
return buffer.poll();
}
public int size() {
return buffer.size();
}
@Override
protected void finalize() throws Throwable {
super.finalize();
channelWeakHashMap.remove(getId());
}
}
class GoChannelObject<E> {
long channel_id;
E data;
boolean belongsTo(GoChannel channel) {
return channel != null && channel_id == channel.id;
}
}
abstract static class GoSelectConsumer{
abstract void accept(GoChannelObject t);
}
}
然后我們可以這樣使用它:
GoChannelPool pool = GoChannelPool.getDefaultInstance();
final GoChannelPool.GoChannel<Integer> numberCh = pool.newChannel();
final GoChannelPool.GoChannel<String> stringCh = pool.newChannel();
final GoChannelPool.GoChannel<String> otherCh = pool.newChannel();
ExecutorService executorService = Executors.newCachedThreadPool();
int times;
times = 2000;
final CountDownLatch countDownLatch = new CountDownLatch(times * 2);
final AtomicInteger numTimes = new AtomicInteger();
final AtomicInteger strTimes = new AtomicInteger();
final AtomicInteger defaultTimes = new AtomicInteger();
final int finalTimes = times;
executorService.submit(new Runnable() {
@Override
public void run() {
for (int i = 0; i < finalTimes; i++) {
numberCh.offer(i);
try {
Thread.sleep((long) (Math.random() * 10));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
executorService.submit(new Runnable() {
@Override
public void run() {
for (int i = 0; i < finalTimes; i++) {
stringCh.offer("s"+i+"e");
try {
Thread.sleep((long) (Math.random() * 10));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
int otherTimes = 3;
for (int i = 0; i < otherTimes; i++) {
otherCh.offer("a"+i);
}
for (int i = 0; i < times*2 + otherTimes; i++) {
pool.select(new GoChannelPool.GoSelectConsumer() {
@Override
void accept(GoChannelPool.GoChannelObject t) {
// The data order should be randomized.
System.out.println(t.data);
countDownLatch.countDown();
if (t.belongsTo(stringCh)) {
strTimes.incrementAndGet();
return;
}
else if (t.belongsTo(numberCh)) {
numTimes.incrementAndGet();
return;
}
defaultTimes.incrementAndGet();
}
});
}
countDownLatch.await(10, TimeUnit.SECONDS);
/**
The console output of data should be randomized.
numTimes.get() should be 2000
strTimes.get() should be 2000
defaultTimes.get() should be 3
*/
並且要注意,只有當頻道屬於同一個GoChannelPool時才選擇有效,或者只使用默認的GoChannelPool(但是如果太多頻道共享同一個GoChannelPool,性能會降低)
我記得當我剛接觸 Java 時,不知道線程可以共享進程的 memory,我會讓我的線程使用(TCP/本地)Sockets 進行通信。也許這也可以工作。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.