Nonblocking خوارزمية توليد فريدة من نوعها الأرقام السالبة

StackOverflow https://stackoverflow.com/questions/579925

سؤال

كنت مؤخرا ريفاكتوريد قطعة من التعليمات البرمجية المستخدمة لتوليد فريدة من نوعها الأرقام السالبة.
تحرير: المواضيع متعددة الحصول على هذه المعرفات و إضافة مفاتيح ديسيبل ؛ أرقام تحتاج إلى أن تكون سلبية يمكن التعرف عليها بسهولة -في نهاية جلسة اختبار أنهم إزالتها من DB.

بلدي جافا خوارزمية يبدو مثل هذا:

private final Set<Integer> seen = Collections.synchronizedSet(new HashSet<Integer>());
public Integer generateUniqueNegativeIds() {
    int result = 0;
    do {
        result = random.nextInt();
        if (result > 0) {
            result *= -1;
        }
    } while (!seen.add(result));
    return result;
}

رمز هيكل أعلاه ، مع المضاربة بالإضافة إلى مجموعة "إعادة المحاولة" حلقة يجعلني أعتقد أن هناك ما يعادل nonblocking خوارزمية أن يستبدل متزامنة مع مجموعة أي من الذرية المتغيرات.

قمت ببعض المحاولات الرامية إلى إعادة كتابة باستخدام الذرية المتغيرات ولكن كل فشل multithread الهجوم الاختبار.

هل هناك أنيقة nonblocking ما يعادل?

تحرير: لمجرد الفضول هنا هو محاولة معيبة باستخدام ذرية صحيح كحارس

private final AtomicInteger atomi = new AtomicInteger(0);
public Integer generateUniqueNegativeIdsWithAtomicAlgo() {
    boolean added = false;
    int result = 0;
    do {
        result = random.nextInt();
        if (result > 0) {
            result *= -1;
        }
        if (atomi.compareAndSet(0, result)) {
            added = cache.add(result);
        }   
    } while (!added);
    return atomi.getAndSet(0);
}

تحرير: اختبار تسخير أدناه:

public static void main(String[] args) {
    final int NUMBER_OF_THREADS = 10000;
    final Set<Integer> uniques = Collections.synchronizedSet(new HashSet<Integer>());
    final List<Integer> positives = Collections.synchronizedList(new ArrayList<Integer>());
    final NegativeUniqueIdGenerator nuig = new NegativeUniqueIdGenerator();
    Thread[] workers = new Thread[NUMBER_OF_THREADS];
    long start = System.nanoTime();
    for (int i = 0; i < workers.length; i++) {
        Runnable runnable = new Runnable() {
            public void run() {
                int number = nuig.generateUniqueNegativeIds();
                if (number > 0) {
                    positives.add(number);
                }
                uniques.add(number);
            }
        };
        workers[i] = new Thread(runnable);
        workers[i].start();
    }
    for (int i = 0; i < workers.length; i++) {
        try {
            workers[i].join();
        } catch (InterruptedException ie) {}
    }
    long end = System.nanoTime();
    System.out.println(String.format("duration = %dns", (end - start)));
    System.out.println(String.format("#threads = %d", NUMBER_OF_THREADS));
    System.out.println(String.format("#uniques = %d", uniques.size()));
    System.out.println(String.format("#positives = %d", positives.size()));
    System.out.println(String.format("#duplicates = %d", NUMBER_OF_THREADS - uniques.size()));
    System.out.println(String.format("ratio = %f",
            ((double) NUMBER_OF_THREADS - uniques.size())
                    / NUMBER_OF_THREADS));
    assert uniques.size() == NUMBER_OF_THREADS;
}
هل كانت مفيدة؟

المحلول

من متطلبات لقد أعطيت أنا شخصيا فقط استخدام متوسطة الجودة مولد رقم عشوائي عليك أن تعرف لن تنتج مكررة في عدد من الأرقام الفريدة التي تحتاجها.إلا إذا كان لديك إضافية شرط لم تذكر ، يبدو مبالغة للحفاظ على تعيين جميع ولدت سابقا الأرقام.

على سبيل المثال, استخدام 32 بت XORShift مولد سوف تنتج 2^31 السلبية 4 بايت الصحيحه في "عشوائية" النظام قبل تكرار النمط.إذا كنت بحاجة إلى المزيد من الأرقام من ذلك, ربما كنت لا تريد أن تكون في وضعها في تجزئة مجموعة على أي حال.حتى شيء من هذا القبيل (تحذير:قبالة أعلى الرأس لم تختبر رمز...):

int seed = (int) System.nanoTime();
final int origSeed = seed;

public int nextUniqueNegativeNumber() {
  int n = seed;
  do {
    n ^= (n << 13);
    n ^= (n >>> 17);
    n ^= (n << 5);
    seed = n;
    if (n == origSeed) {
      throw new InternalError("Run out of numbers!");
    }
  } while (n > 0);
  return n;
}

سأترك للقارئ أن تحويل "البذور" استخدام AtomicInteger إذا التزامن هو ضروري...

تحرير:في الواقع لتحسين المتزامنة الحالة ربما كنت فقط أريد أن أكتب مرة أخرى إلى "البذور" بعد الحصول على التالي السلبية عدد.

حسنا بواسطة مطلب شعبي ، الذرية النسخة ثم يكون شيئا من هذا القبيل:

  AtomicInteger seed = new AtomicInteger((int) System.nanoTime());

  public int nextUniqueNegativeNumber() {
    int oldVal, n;
    do {
      do {
        oldVal = seed.get();
        n = oldVal ^ (oldVal << 13); // Added correction
        n ^= (n >>> 17);
        n ^= (n << 5);
      } while (seed.getAndSet(n) != oldVal);
    } while (n > 0);
    return n;
  }

نصائح أخرى

إذا لم تكن تشعر بالقلق إزاء العشوائية، فيمكنك تقليل عداد، مثل هذا:

private final AtomicInteger ai=new AtomicInteger(0);

public int nextID() {
  return ai.addAndGet(-1);
}

يحرر:

لأرقام عشوائية، يمكنك فقط استخدام الحل الخاص بك واستخدامك على سبيل المثال. concurrenthmap أو concurrentskiptset بدلا من المزامنة. عليك أن تضمن استخدام مؤشرات الترابط المختلفة مثيلات مختلفة من المولد العشوائي، وأن هذه المولدات غير مرتبطة.

الإجابات الأخرى التي تشير إلى استخدام عداد ممتازة، ولكن إذا كانت القدرة على القدرة على التنبؤ غير المستقبلي (أو على الأقل) يكون هام، يجب أن تكون الخوارزمية الأصلية على ما يرام.

لماذا ا؟

في الأساس، فإن الاحتمال الذي ستحصل عليه عددا صحيحا جدا (جدا) (جدا) صغيرا، مقسوما تقريبا على عدد الأعداد الصحيحة التي لم ترها بعد. إذا كنت قد ولدت بالفعل N الأرقام، وقت التشغيل المتوقع للخوارزمية هو خطي تقريبا في N مع معامل 1/2 ^ 32، مما يعني أنه سيتعين عليك توليد أكثر من مليار أرقام فقط للحصول على وقت التشغيل المتوقع لتتجاوز 2 تكرار للحلقة! في الممارسة العملية، سيحقق التحقق من مجموعة وجود عدد معين أكثر بكثير لتمديد وقت تشغيل خوارزمية من إمكانية تكرار الحلقة (حسنا، إلا إذا كنت تستخدم HashSet ربما - أنسى ما هو وقت التشغيل مقاربه).

لما يستحق، فإن العدد الدقيق المتوقع من الحلقة

2^64/(2^32 - N)^2

بعد أن قمت بإنشاء مليون أرقام، يعمل هذا إلى 1.00047 - وهو ما يعني ذلك، على سبيل المثال، لتوليد أرقام 10001 إلى 100.002000، من المحتمل أن تحصل عليها واحد كرر الرقم، المجموع, ، في كل تلك المكالمات.

الحل الأنيق لجميع المتطلبات المدرجة، بقدر ما أستطيع أن أقول، هو مجرد انخفاض قيمة تبدأ في -1. ومع ذلك، أظن أنك لم تدرج جميع المتطلبات.

أود أن أجمع بين إجابة OP مع JPalecek لإعطاء:

private final AtomicInteger ai=new AtomicInteger(0);

public int nextID() {
    return ai.addAndGet(-1 - random.nextInt(1000));
}

يحتوي Lib عالية النطاق على STBLOCKINGHASHSHASHSHASHSHASHSHASHST يمكنك استخدامها. ما عليك سوى استبدال مثيل STIES الخاص بك بمثيل NonBlockinghashshashashass وكلها مجموعة.

http://sourceforge.net/projects/high-scale-lib.

أعتقد أن ما تعنيه هو عدم الحجب والراديو.

تعديل: (يحل محل أصلي لأن هذا أفضل بكثير)

الخيار القائم على الخيوط الذي هو في الواقع أداء جميل يأتي إلى الذهن (على الأقل المزيد من الأداء من الأصلي). إذا قمت بإنشاء خريطة تتجهلية ضعيفة مع كائن مؤشر ترابط ككائن "المفتاح" وكما وضع "القيمة" كائنا بقدرة على تصنيع سلسلة من، قل، 1000 أرقام من مجموعة معينة.

بهذه الطريقة تقوم بتعيين كل مؤشر ترابط هو 1000 نطاق رقمي لتخصيص منه. عندما ينفد الكائن من الأرقام، هل قمت بإرجاع رقم غير صالح (0؟) وستعرف أنك يجب عليك تخصيص مجموعة جديدة لهذا الكائن.

لن يكون هناك مزامنة أي شيء في أي مكان (تحرير: يصيح، كانت خاطئة بعض الشيء. انظر أدناه)، ستقوم خريطة التجزئة الضعيفة تلقائيا خالية من الخيوط التي تم تدميرها (بدون صيانة خاصة) وستكون الجزء الأبطأ بحثا عن علامة تجزئة واحدة للخيط وهو في الواقع سريع جدا.

احصل على مؤشر ترابط التشغيل الحالي مع:

Thread currThread=Thread.getCurrentThread();

أيضا يمكن أن أكون مخطئا وقد تحتاج فقط إلى جعل الطريقة متزامنة، ثم هذا سيعمل:

int n=-1;
synchronized int getNegativeNumber() {
    return n--;
}

ذهبت إلى الأمام وكتبت (في بعض الأحيان تعثرت هذه الأشياء في رأسي حتى أفعل ذلك، وطالما فعلت ذلك على أي حال، قد أشرك كذلك). لم يتم اختبارها وكلها، لكنني متأكد من أنه يجب إغلاقه إذا لم يتم تشغيله مباشرة خارج الصندوق. مجرد فئة واحدة مع طريقة ثابتة واحدة للاتصال للحصول على رقم سلبي فريد. (أوه، وأنا بحاجة إلى بعض المزامنة ولكن سيتم استخدامها فقط .001٪ من الوقت).

أتمنى أن تكون هناك طريقة لإنشاء كتلة شفرة مرتبطة بدلا من Inline مثل هذا دون الخروج من الموقع - آسف على الطول.

package test;

import java.util.WeakHashMap;

public class GenNumber {
    // Static implementation goes first.
    private static int next = -1;
    private static final int range = 1000;

    private static WeakHashMap<Thread, GenNumber> threads = new WeakHashMap<Thread, GenNumber>();

    /**
     * Generate a unique random number quickly without blocking
     * 
     * @return the random number < 0
     */
    public static int getUniqueNumber() {
        Thread current = Thread.currentThread();
        int next = 0;

        // Have to synchronize some, but let's get the very
        // common scenario out of the way first without any
        // synchronization. This will be very fast, and will
        // be the case 99.9% of the time (as long as range=1000)
        GenNumber gn = threads.get(current);
        if (gn != null) {
            next = gn.getNext();
            if (next != 0)
                return next;
        }

        // Either the thread wasn't found, or the range was
        // used up. Do the rest in a synchronized block.
        // The three lines tagged with the comment "*" have
        // the potential to collide if this wasn't synchronized.
        synchronized (threads) {
            if (gn == null) {
                gn = new GenNumber(next -= range); // *
                threads.put(current, gn); // *
                return gn.getNext(); // can't fail this time
            }
            // now we know the range has run out

            gn.setStart(next -= range); // *
            return gn.getNext();
        }
    }

    // Instance implementation (all private, nobody needs to see this)
    private int start;
    private int count;

    private GenNumber(int start) {
        setStart(start);
    }

    private int getNext() {
        if (count < range)
            return start - count;
        return 0;
    }

    private GenNumber setStart(int start) {
        this.start = start;
        return this;
    }
}

لقد ضربني فقط أنه بدلا من كتلة واحدة متزامنة واحدة يمكن استبدالها بمقدار 2 صغيرة جدا متزامنة على كائنات مختلفة، واحدة ل "+ = عدد" واحد وواحد ل. dut (). إذا كانت الاصطدامات لا تزال تباطؤك، فقد تساعدك (على الرغم من أن الاصطدام لا تزال تبطئك (حقا ؟؟؟) ستكون أفضل تقديم العدد.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top