/** * Copyright 2013, Landz and its contributors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package z.channel; import static z.util.Contracts.contract; import static z.util.Unsafes.*; /** * A bounded multi-producer/multi-consumer LongHyperLoop. * <p> * Different from MP Disruptor, it is done in "queue" style. That is, the out * element is only consumed by one of all consumers, not all of them * (broadcasting). * <p> * references implementation is Dmitry Vyukov's Bounded MPMC queue: * http://www.1024cores.net/home/lock-free-algorithms/queues/bounded-mpmc-queue * <p> * Note:<p> * 1. this is NOT a "totally lock-free" algorithm. But, it's still much * better than the lock-based algorithm. * 2. for convenience, now we use {@link Long#MIN_VALUE} to present NULL * to receive from {@link MPMCQueue}(otherwise we should an exception). * */ public class MPMCQueue { /** * for convenience, now we use {@link Long#MIN_VALUE} to present nothing could * be received from {@link MPMCQueue}(otherwise we should an exception). */ public static final long NULL = Long.MIN_VALUE; private static final int SIZESHIFT_BUFFERSLOT = 4;//2*SIZE_LONG_TYPE private static final int SIZE_LONG_TYPE = 8; private final long addressRaw; private final long addressMPMCQueue; private final int nBufferSlots; private final int bufferSlotMask; private final long addrWriteCursor; private final long addrReadCursor; private final long addrBuffer; // //1 boolean(byte) per slot,true is un-pub-ed, false is pub-ed // private final long addrMarkBuffer; /** * @param nBufferSlots the size of internal buffer in slot unit */ public MPMCQueue(int nBufferSlots) { contract(()->nBufferSlots>=2); contract(()->Integer.bitCount(nBufferSlots)==1); this.nBufferSlots = nBufferSlots; this.bufferSlotMask = nBufferSlots - 1; //======================================================== int bufferSize = nBufferSlots<<SIZESHIFT_BUFFERSLOT; addressRaw = systemAllocateMemory( bufferSize + SIZE_CACHE_LINE_PADDING*4 + SIZE_CACHE_LINE); addressMPMCQueue = nextCacheLineAlignedAddress(addressRaw); contract(() -> isCacheLineAligned(addressMPMCQueue)); this.addrWriteCursor = addressMPMCQueue + SIZE_CACHE_LINE_PADDING; contract(() -> isCacheLineAligned(addrWriteCursor)); this.addrReadCursor = addrWriteCursor + SIZE_CACHE_LINE_PADDING; contract(() -> isCacheLineAligned(addrReadCursor)); this.addrBuffer = addrReadCursor + SIZE_CACHE_LINE_PADDING; contract(() -> isCacheLineAligned(addrBuffer)); // this.addrMarkBuffer = addrBuffer + bufferSize + SIZE_CACHE_LINE_PADDING; // contract(() -> isCacheLineAligned(addrMarkBuffer)); //clear for (int i = 0; i < nBufferSlots; i++) { UNSAFE.putLong( addrBuffer + (i << SIZESHIFT_BUFFERSLOT)+SIZE_LONG_TYPE, i); } UNSAFE.putLong(addrWriteCursor, 0L); UNSAFE.putLong(addrReadCursor, 0L); } /** * To add one long value to current {@link MPMCQueue}. * The added long value will stored in the {@link MPMCQueue} * in FIFO order. * <p> * @return - true if successfully added to the currrent * {@link MPMCQueue}, * or false if currrent {@link MPMCQueue} * is full. */ public boolean offer(long value) { long writeCursor, addrWriteIndex; writeCursor = UNSAFE.getLongVolatile(null,addrWriteCursor); for (;;) { addrWriteIndex = addrBuffer + ((writeCursor & bufferSlotMask) << SIZESHIFT_BUFFERSLOT); long seq = UNSAFE.getLongVolatile(null, addrWriteIndex+SIZE_LONG_TYPE); long dif = seq - writeCursor; if (dif == 0) { if (UNSAFE.compareAndSwapLong(null, addrWriteCursor, writeCursor, writeCursor + 1)) break; } else if (dif < 0) { return false; } else { writeCursor = UNSAFE.getLongVolatile(null,addrWriteCursor); } } UNSAFE.putLongVolatile(null, addrWriteIndex,value); UNSAFE.putLongVolatile(null, addrWriteIndex + SIZE_LONG_TYPE, writeCursor + 1); return true; } /** * To Retrieves and removes one long value from current * {@link MPMCQueue}. * * This long value is sent before in FIFO order. * <p> * @return - one long value if currrent {@link MPMCQueue} * holds at least one value, * or {@link #NULL} if currrent {@link MPMCQueue} * is empty. Please use the {@link #NULL} to check your result. */ public long poll() { long readCursor, addrReadIndex; readCursor = UNSAFE.getLongVolatile(null,addrReadCursor); for (; ; ) { addrReadIndex = addrBuffer + ((readCursor & bufferSlotMask) << SIZESHIFT_BUFFERSLOT); long seq = UNSAFE.getLongVolatile(null, addrReadIndex + SIZE_LONG_TYPE); long dif = seq - (readCursor + 1); if (dif == 0) { if (UNSAFE.compareAndSwapLong(null, addrReadCursor, readCursor, readCursor + 1)) break; } else if (dif < 0) { return NULL; } else { readCursor = UNSAFE.getLongVolatile(null, addrReadCursor); } } long value = UNSAFE.getLongVolatile(null, addrReadIndex); UNSAFE.putLongVolatile(null, addrReadIndex + SIZE_LONG_TYPE, readCursor + nBufferSlots); return value; } @Override public void finalize() { systemFreeMemory(addressRaw); } }