2009-11-17 1 views
10

Usiamo socket molto in un programma su cui lavoro e gestiamo connessioni da un massimo di circa 100 macchine contemporaneamente. Abbiamo una combinazione di I/O non bloccante in uso con una tabella di stato per gestirlo e socket Java tradizionali che utilizzano thread.I/O non bloccante rispetto all'utilizzo di thread (Quanto è problematico il cambio di contesto?)

Abbiamo un bel po 'di problemi con i socket non bloccanti e personalmente mi piace usare i thread per gestire le prese molto meglio. Quindi la mia domanda è:

Quanto risparmio si ottiene utilizzando socket non bloccanti su un singolo thread? Quanto è grave il cambio di contesto coinvolto nell'utilizzo dei thread e quante connessioni simultanee puoi scalare per utilizzare il modello threaded in Java?

risposta

10

I/O e I/O non bloccanti dipendono dal profilo di attività del server. Per esempio. se si utilizzano connessioni di lunga durata e migliaia di client I/O potrebbero diventare troppo costosi a causa dell'esaurimento delle risorse di sistema. Tuttavia, l'I/O diretto che non elimina la cache della CPU è più veloce rispetto all'I/O non bloccante. C'è un buon articolo a riguardo - Writing Java Multithreaded Servers - whats old is new.

Informazioni sul costo dello switch di contesto: è piuttosto un'operazione con chip. Si consideri il semplice test di seguito:

package com; 

import java.util.ArrayList; 
import java.util.List; 
import java.util.Random; 
import java.util.Set; 
import java.util.concurrent.ConcurrentSkipListSet; 
import java.util.concurrent.CountDownLatch; 
import java.util.concurrent.TimeUnit; 
import java.util.concurrent.atomic.AtomicLong; 

public class AAA { 

    private static final long DURATION = TimeUnit.NANOSECONDS.convert(30, TimeUnit.SECONDS); 
    private static final int THREADS_NUMBER = 2; 
    private static final ThreadLocal<AtomicLong> COUNTER = new ThreadLocal<AtomicLong>() { 
     @Override 
     protected AtomicLong initialValue() { 
      return new AtomicLong(); 
     } 
    }; 
    private static final ThreadLocal<AtomicLong> DUMMY_DATA = new ThreadLocal<AtomicLong>() { 
     @Override 
     protected AtomicLong initialValue() { 
      return new AtomicLong(); 
     } 
    }; 
    private static final AtomicLong DUMMY_COUNTER = new AtomicLong(); 
    private static final AtomicLong END_TIME = new AtomicLong(System.nanoTime() + DURATION); 

    private static final List<ThreadLocal<CharSequence>> DUMMY_SOURCE = new ArrayList<ThreadLocal<CharSequence>>(); 
    static { 
     for (int i = 0; i < 40; ++i) { 
      DUMMY_SOURCE.add(new ThreadLocal<CharSequence>()); 
     } 
    } 

    private static final Set<Long> COUNTERS = new ConcurrentSkipListSet<Long>(); 

    public static void main(String[] args) throws Exception { 
     final CountDownLatch startLatch = new CountDownLatch(THREADS_NUMBER); 
     final CountDownLatch endLatch = new CountDownLatch(THREADS_NUMBER); 

     for (int i = 0; i < THREADS_NUMBER; i++) { 
      new Thread() { 
       @Override 
       public void run() { 
        initDummyData(); 
        startLatch.countDown(); 
        try { 
         startLatch.await(); 
        } catch (InterruptedException e) { 
         e.printStackTrace(); 
        } 
        while (System.nanoTime() < END_TIME.get()) { 
         doJob(); 
        } 
        COUNTERS.add(COUNTER.get().get()); 
        DUMMY_COUNTER.addAndGet(DUMMY_DATA.get().get()); 
        endLatch.countDown(); 
       } 
      }.start(); 
     } 
     startLatch.await(); 
     END_TIME.set(System.nanoTime() + DURATION); 

     endLatch.await(); 
     printStatistics(); 
    } 

    private static void initDummyData() { 
     for (ThreadLocal<CharSequence> threadLocal : DUMMY_SOURCE) { 
      threadLocal.set(getRandomString()); 
     } 
    } 

    private static CharSequence getRandomString() { 
     StringBuilder result = new StringBuilder(); 
     Random random = new Random(); 
     for (int i = 0; i < 127; ++i) { 
      result.append((char)random.nextInt(0xFF)); 
     } 
     return result; 
    } 

    private static void doJob() { 
     Random random = new Random(); 
     for (ThreadLocal<CharSequence> threadLocal : DUMMY_SOURCE) { 
      for (int i = 0; i < threadLocal.get().length(); ++i) { 
       DUMMY_DATA.get().addAndGet(threadLocal.get().charAt(i) << random.nextInt(31)); 
      } 
     } 
     COUNTER.get().incrementAndGet(); 
    } 

    private static void printStatistics() { 
     long total = 0L; 
     for (Long counter : COUNTERS) { 
      total += counter; 
     } 
     System.out.printf("Total iterations number: %d, dummy data: %d, distribution:%n", total, DUMMY_COUNTER.get()); 
     for (Long counter : COUNTERS) { 
      System.out.printf("%f%%%n", counter * 100d/total); 
     } 
    } 
} 

ho fatto quattro test per due e dieci scenari filo e mostra la perdita di prestazioni è circa 2,5% (78626 iterazioni per due fili e 76754 per dieci fili), risorse di sistema sono utilizzati da i fili approssimativamente allo stesso modo.

anche 'java.util.concurrent' autori di tempo cambio di contesto supponiamo di essere circa 2000-4000 cicli di CPU:

public class Exchanger<V> { 
    ... 
    private static final int NCPU = Runtime.getRuntime().availableProcessors(); 
    .... 
    /** 
    * The number of times to spin (doing nothing except polling a 
    * memory location) before blocking or giving up while waiting to 
    * be fulfilled. Should be zero on uniprocessors. On 
    * multiprocessors, this value should be large enough so that two 
    * threads exchanging items as fast as possible block only when 
    * one of them is stalled (due to GC or preemption), but not much 
    * longer, to avoid wasting CPU resources. Seen differently, this 
    * value is a little over half the number of cycles of an average 
    * context switch time on most systems. The value here is 
    * approximately the average of those across a range of tested 
    * systems. 
    */ 
    private static final int SPINS = (NCPU == 1) ? 0 : 2000; 
+0

Grazie mille, bello avere un test case. – Benj

+0

Grazie per aver postato il link a "Scrivere server Java con multithreading - cosa è vecchio è nuovo". Avevo dimenticato il suo nome e non riuscivo a trovarlo. –

1

Per le vostre domande, il metodo migliore potrebbe essere quello di creare un programma di test, ottenere dati di misurazione complessi e prendere la decisione migliore in base ai dati. Di solito faccio questo quando cerco di prendere tali decisioni, e aiuta ad avere numeri difficili da portare con te per fare il backup delle tue argomentazioni.

Prima di iniziare, quanti fili stai parlando? E con che tipo di hardware stai usando il tuo software?

+0

Il programma su cui lavoro è peer-to-peer in cui un peer potrebbe parlare con oltre 100 utenti. I peer possono essere Linux/Windows/Mac (vari gusti) e generalmente funzioneranno su PC in genere PC con una buona definizione in un ambiente di ufficio (vale a dire più di 2 cpus). – Benj

1

Per 100 connessioni sono non rischiano di avere un problema con il blocco IO e l'utilizzo due thread per connessione (uno per leggere e scrivere) Questo è il modello più semplice IMHO.

Tuttavia, è possibile trovare utilizzando JMS è un modo migliore per gestire le connessioni. Se si utilizza qualcosa come ActiveMQ è possibile consolidare tutte le connessioni.