|
|||||||
|
|
|
![]() |
|
|
Strumenti |
|
|
#1 |
|
Senior Member
Iscritto dal: Dec 2000
Messaggi: 501
|
[Java] Dubbio su Multi-Thread e Multi-Core
Salve a tutti,
sto realizzando un software che esegue elaborazioni più o meno semplici su un elevato numero di oggetti, quindi per ottimizzarne l'esecuzione ho cercato di utilizzare i Thread. Ovviamente ho a disposizione macchine multicore per l'esecuzione del codice. Ho notato però che l'occupazione dei core non è bilanciata, ma dopo una fase di startup in cui sembra esserlo (ad esempio: 4 core occupati all'incirca al 25%) si arriva a regime ad una occupazione del 100% di un solo core, gli altri sono allo 0%. Cerco di spiegare a grandi linee l'algoritmo: - Si alternano fasi in cui esiste solo il main thread a fasi in cui ne ho molti - Su linux, tramite il comando top vedo in effetti questa alternanza di fasi, che oscillano da un solo core occupato a tutti i core occupati come detto sopra, ma solo nei primi minuti di elaborazione, a regime sembra prevalere un solo core al 100% - I riferimenti alle istanze degli oggetti da elaborare sono memorizzate in un ArrayList, che viene opportunamente diviso dal numero di thread che voglio lanciare, dopodichè lancio i thread invocando il metodo start() e poi il metodo join() per far si che l'esecuzione del codice non vada avanti finché TUTTI non hanno completato la loro elaborazione. - Ovviamente, se il carico di lavoro eseguito da ogni thread non è simile, si può verificare la situazione in cui i thread più veloci devono restare in attesa dei thread più lenti. - Immagino che una situazione del genere (molto probabile, tra l'altro) possa portare all'occupazione al 100% di un solo core contro lo 0% degli altri (caso estremo in cui è rimasto un solo thread e tutti gli altri lo aspettano). - Le operazioni eseguite da ogni thread possono coinvolgere anche metodi e oggetti condivisi, quindi ho dovuto usare il synchronize. Altra ragione che porta i Thread a restare in attesa del rilascio della risorsa condivisa... Ecco, la situazione a grandi linee è questa. Quello che mi chiedo è: 1) Se ho 4 core ed istanzio ad esempio 100 Thread, questi come vengono eseguiti? La CPU se ne prende in carico, ad esempio, 4 e gli altri 96 sono in attesa di essere eseguiti? Quindi se ho uno dei primi 4 thread in attesa del rilascio di una risorsa condivisa, uno degli altri 96 può essere avviato? Oppure i 100 Thread vengono tutti avviati nello stesso istante e procedono in parallelo e quindi si troveranno TUTTI (meno uno) in attesa della risorsa condivisa? 2) Ho visto che esistono comandi come yeld() e wait() per far si che un Thread rilasci le risorse ad un altro, ma poi va risvegliato con notify(). Dovrei forse usare yeld() prima di tentare di accedere alla risorsa condivisa? 3) Penso che il modo per ridurre il tempo di esecuzione totale sia proprio quello di far si che ogni core sia sempre ben occupato, facendo in modo che non appena un Thread si trova a dover attendere il rilascio della risorsa condivisa, se ne attivi subito un altro, per non sprecare cicli di clock... Qualcuno più esperto in programmazione parallela ha dei consigli? P.S. Tra l'altro, non mi convince molto il fatto che l'occupazione arrivi solo al 25%. Ho fatto alcune classi di test per prendere mano con il multi thread e in questi casi semplici ho verificato che tutti i core sono occupati al 100% fino alla fine dell'esecuzione. Mentre nell'algoritmo reale cio' non accade, eppure la strategia bene o male è quella, cambia solo l'elaborazione all'interno del run(). Nei casi di test gli faccio eseguire un paio di operazioni algebriche, nel caso reale invoco altri metodi... Ultima modifica di Lim : 12-01-2012 alle 08:10. Motivo: Aggiunto un'ulteriore chiarimento |
|
|
|
|
|
#2 |
|
Senior Member
Iscritto dal: Jul 2011
Messaggi: 381
|
Ciao, cercherò di essere il più conciso possibile.
Premessa: un thread può essere in diverse "code": - coda dei thread in esecuzione (una coda per ogni core costituita da max 1 elemento) - coda dei thread pronti (in attesa di esser mandati in esecuzione su una coda di esecuzione) - N code dei thread bloccati per ogni risorsa condivisa (in attesa di accedere ad una risorsa condivisa) 1) Se hai 4 core, 4 thread possono potenzialmente essere in esecuzione contemporaneamente, MA, potrebbe succedere che, poiché tutt i thread devono accedere ad un risorsa condivisa (es con il metodo synchronized) essi vadano tutti a bloccarsi quindi saresti nella situazione di 1 thread in esecuzione e 99thread bloccati. Però potresti anche trovarti nel caso in cui hai 1 thread in esecuzione nella sezione critica e altri 3 thread che ci "stanno per arrivare" es. int b=10; <--- th2, th3, th4 sono quì synchronized(){ ... <----- th1 è quì a=1; ... } In questo caso hai effettivamente 4 thread in esecuzione. Tuttavia possono avvenire infiniti di casi differenti. Esempio: 4 thread in esecuzione, 50 pronti e 46 bloccati su possibili diverse code di bloccaggio. Attenzione: quando usi il synchronized(...) esso è bloccante solo se c'è un thread in esecuzione dentro l'oggetto a cui si riferisce la synchronized. Diverse synchronized su oggetti diversi formano code di bloccaggio differenti. Non rispondo alle altre domande per non confonderti di più nel caso non sia stato abbastanza chiaro. PPS. se vuoi ottimizzare il codice al massimo è sufficiente che non usi sezioni critiche. Ovviamente mantenendo la consistenza dei dati. Quindi, meno sezioni critiche -> meno thread bloccati -> più thread nella coda dei processi pronti -> più thread in esecuzione. Il "meno" non è inteso come quantità di sezioni critiche, ne basta una che ti blocca tutto.
__________________
Concluso positivamente con: Kamzata, Ducati82, Arus, TheLastRemnant, ghost driver, alexbull1, DanieleRC5, XatiX Ultima modifica di starfred : 12-01-2012 alle 10:47. |
|
|
|
|
|
#3 | ||
|
Senior Member
Iscritto dal: Dec 2000
Messaggi: 501
|
Quote:
Quote:
Un momento, tu parli di oggetto. Vuoi dire che gli altri thread non potrebbero accedere ad altri metodi NON sincronizzati dell'oggetto in questione? |
||
|
|
|
|
|
#4 |
|
Senior Member
Iscritto dal: Jul 2011
Messaggi: 381
|
no voglio dire, se hai 2 thread che fanno:
il primo thread: synchronized (a){} il secondo thread: synchronized(b){} Essi sono eseguiti in parallelo senza nessun bloccaggio
__________________
Concluso positivamente con: Kamzata, Ducati82, Arus, TheLastRemnant, ghost driver, alexbull1, DanieleRC5, XatiX |
|
|
|
|
|
#5 |
|
Senior Member
Iscritto dal: Oct 2007
Città: Padova
Messaggi: 4131
|
*edit
__________________
As long as you are basically literate in programming, you should be able to express any logical relationship you understand. If you don’t understand a logical relationship, you can use the attempt to program it as a means to learn about it. (Chris Crawford) Ultima modifica di banryu79 : 12-01-2012 alle 11:09. |
|
|
|
|
|
#6 | |
|
Senior Member
Iscritto dal: Dec 2000
Messaggi: 501
|
Quote:
Ho un oggetto che ha diversi metodi NON sincronizzati ed uno solo sincronizzato. Ogni Thread deve accedere a questo stesso oggetto ed invocare alcuni suoi metodi, ad un certo punto dell'esecuzione, invocano il metodo sincronizzato. Il primo che ci arriva lo blocca. Quello che mi chiedo io è se gli altri thread + lenti possono continuare ad invocare i metodi non sincronizzati (finchè non arrivano a quello bloccato) o si bloccano subito perché stanno utilizzando le funzionalità (metodi) dello stesso oggetto del thread bloccante? Spero di essermi spiegato |
|
|
|
|
|
|
#7 |
|
Senior Member
Iscritto dal: Jul 2011
Messaggi: 381
|
Sperando di aver capito, ti riferisci ad una situazione iniziale dove hai:
100 thread totali, 4 in esecuzione, 0 bloccati. Codice:
// 4 thread in esecuzione parallela
int b=10; <--- th1, th2, th3, th4 sono quì
c=2
d=3
synchronized(){
...
a=1;
...
}
Codice:
//Hai sempre 4 thread in esecuzione parallela
int b=10; <--- th2, th3, th4 sono quì
c=2
d=3
synchronized(){
... <--- th1 è quì
a=1;
...
}
Codice:
int b=10; th5 <--- è quì
c=2
d=3 <--- th3, th4 sono quì
synchronized(){
... <--- th1 è quì
a=1;
...
}
Ovviamente può accadere che se la sezione critica è molto lunga, prima o poi tutti i thread pronti finiscono nella coda bloccati, a quel punto avremmo un solo thread in esecuzione. Quando esso finirà la sezione critica, risveglierà un thread dalla coda bloccati e, ad esclusione di particolari metodi di programmazione, lo rimette in quella pronti successivamente lo scheduler selezionerà un thread dalla coda del processi pronti e lo manderà in esecuzione. Quindi avremo 2 thread in esecuzione 98 thread bloccati Ps. se hai utilizzato metodi wait() e notify() la situazione è un po' differente...
__________________
Concluso positivamente con: Kamzata, Ducati82, Arus, TheLastRemnant, ghost driver, alexbull1, DanieleRC5, XatiX |
|
|
|
|
|
#8 | |
|
Senior Member
Iscritto dal: Dec 2000
Messaggi: 501
|
Quote:
Penso che la situazione in cui mi trovo è proprio questa. In effetti nei primi istanti ho un'occupazione equa dei core, mentre a regime ne ho solo uno occupato al 100%. Devono aver raggiunto tutti il punto critico e sono tutti nella coda dei bloccati, quindi da un'esecuzione potenzialmente parallela, sono tornato ad una sequenziale, senza vantaggi evidenti. Devo rivedere il codice in modo sostanziale temo... Mi consigli qualche approccio specifico? o devo semplicemente ridurre il più possibile la presenza di risorse condivise e metodi synchronized? e la durata/complessita delle eventuali porzioni bloccanti? PS Al momento non ho utilizzato né wait() né notify(). |
|
|
|
|
|
|
#9 |
|
Senior Member
Iscritto dal: Jul 2011
Messaggi: 381
|
Se effettivamente avviene il bloccaggio dei thread, per prima cosa ti consiglio di non utilizzare 100 thread
Per quel che riguarda la sezione critica il tutto dipende dal tipo di risorsa condivisa e distinguere tra operazioni distruttive "scritture" e operazioni non distruttive "letture". Per esempio il classico problema lettori-scrittori ti può far vedere come posso avere più lettori paralleli su una risorsa condivisa e con un solo scrittore attivo alla volta. Un altro consiglio è, ma questo vale per la programmazione in generale, scrivere è meno possibile su disco ed in generale su IO. Se dentro la sezione critica hai una write su un file, stai sicuro al 99% che tutti i thread si bloccano in attesa dell'operazione.
__________________
Concluso positivamente con: Kamzata, Ducati82, Arus, TheLastRemnant, ghost driver, alexbull1, DanieleRC5, XatiX |
|
|
|
|
|
#10 | |||
|
Senior Member
Iscritto dal: Dec 2000
Messaggi: 501
|
Quote:
Mi sembra di intuire che tu suggerisci al max 2 per ogni core, verificherò meglio dopo la revisione del codice. Quote:
Uno dei due la gestisce tramite Vector. Ho verificato che anche la sola lettura di un elemento del Vector rallenta in modo pazzesco l'esecuzione. Conosci altre strutture dati che non blocchino anche in lettura? Quote:
|
|||
|
|
|
|
|
#11 |
|
Senior Member
Iscritto dal: Jul 2011
Messaggi: 381
|
Quello che dico io è: prova a levare tutte le sezioni critiche, leggi una variabile condivisa e fai delle operazioni random su di essa.
es. 4 thread in parallelo che eseguono Codice:
while(1){
int b=int_condiviso;
b=b+rand();
}
Se invece fai Codice:
synchronized(){
while(1){
int b=int_condiviso;
b=b+rand();
}
}
I codici sono corretti entrambi ma presentano sostanziali prestazioni. Il fatto che sia corretto mettere int_condiviso fuori dal synchronized() è dovuto al fatto che noi non stiamo modificando tale variabile ma la stiamo solo leggendo. Spero di averti dato uno spunto di riflessione sul miglioramento da fare sul codice.
__________________
Concluso positivamente con: Kamzata, Ducati82, Arus, TheLastRemnant, ghost driver, alexbull1, DanieleRC5, XatiX |
|
|
|
|
|
#12 | |
|
Senior Member
Iscritto dal: Oct 2007
Città: Padova
Messaggi: 4131
|
@Lim: forse è inutile dirlo ma questo uso del synchronized (lock implicito):
Codice:
class ClassName {
private Type field1;
private AnotherType field2;
public synchronized void method1() {
// doSomething...
// doSomething with field1
// doSomethingElse...
// doSomething with field2
}
}
Codice:
class ClassName {
private Type field1;
private AnotherType field2;
public void method1() {
synchronized(this) {
// doSomething...
// doSomething with field1
// doSomethingElse...
// doSomething with field2
}
}
}
Quote:
Per riprende con l'esempio qui sopra, il lock sul "this" sta regolando l'accesso a entrambi i campi (field1 e field2). Ma supponiamo che non occorra regolare l'accesso in maniera esclusiva su filed1 (AType) ma solo su filed2 (AnotherType). Si potrebbe allora fare così: Codice:
class ClassName {
private Type field1;
private AnotherType field2;
public void method1() {
// doSomething...
// doSomething with field1
// doSomethingElse...
synchronized(this) {
// doSomething with field2
}
}
}
Codice:
class ClassName {
private Type field1;
private AnotherType field2;
private Object filed2Lock;
public void method1() {
// doSomething...
// doSomething with field1
// doSomethingElse...
synchronized(filed2Lock) {
// doSomething with field2
}
}
}
__________________
As long as you are basically literate in programming, you should be able to express any logical relationship you understand. If you don’t understand a logical relationship, you can use the attempt to program it as a means to learn about it. (Chris Crawford) Ultima modifica di banryu79 : 12-01-2012 alle 13:42. |
|
|
|
|
|
|
#13 | |
|
Senior Member
Iscritto dal: Dec 2000
Messaggi: 501
|
Quote:
Grazie per l'ottimo chiarimento. Diciamo che per semplicità ho utilizzato dei lock impliciti per risolvere alcuni problemi di concorrenza. Quello che voglio fare ora infatti è un'ottimizzazione più fine del codice. Dall'esempio di codice che hai postato però un piccolo dubbio me l'hai fatto sorgere. Giustamente, non applico il lock al metodo, ma ai campi che lui manipola. Quindi il lock sul this blocca TUTTI i campi dell'istanza, anche se sono invocati/manipolati da una qualunque altra porzione di codice (altri metodi della stessa istanza, da altre classi ecc...). Invece applicando il lock ad un campo specifico (field2Lock) sincronizzo solo in base ad esso, quindi gli altri campi restano utilizzabili, giusto? |
|
|
|
|
|
|
#14 | ||
|
Senior Member
Iscritto dal: Oct 2007
Città: Padova
Messaggi: 4131
|
Quote:
Dice: caro thread in esecuzione, prima di poter entrare e procedere nella sezione critica, io devo essere disponibile ed entrare in tuo possesso esclusivo. Quando esci dalla sezione critica, io non sono più in tuo possesso e torno disponibile. Quote:
Esempio: Codice:
class ClassName {
private Object field1 = bla...
private Object field2 = bla...
public synchronized void method1() {
// operate on field1...
// operate on field2...
}
public synchronized void method2() {
// operate on field1...
// operate on field2...
}
public void method3() {
// operate on field2...
}
}
A titolo di ulteriore esempio, visto che qui sopra ci interessa rendere esclusivo tra i vari thread solo l'uso (gli accessi) di field1 mentre field2 no (per qualsivoglia ragione che ora non ha importanza) si può dire così: Codice:
class ClassName {
private Object field1 = bla...
private Object field2 = bla...
public void method1() {
synchronized(this) {
// operate on field1
}
// operate on field2
}
public void method2() {
synchronized(this) {
// operate on field1
}
// operate on field2
}
public void method3() {
// operate on field2
}
__________________
As long as you are basically literate in programming, you should be able to express any logical relationship you understand. If you don’t understand a logical relationship, you can use the attempt to program it as a means to learn about it. (Chris Crawford) Ultima modifica di banryu79 : 12-01-2012 alle 14:45. |
||
|
|
|
|
|
#15 |
|
Senior Member
Iscritto dal: Jul 2011
Messaggi: 381
|
Ci tengo a precisare in ogni caso il synchronized si riferisce sempre e comunque ad oggetti e non a porzioni di codice. Per tutto il resto sono d'accordo.
__________________
Concluso positivamente con: Kamzata, Ducati82, Arus, TheLastRemnant, ghost driver, alexbull1, DanieleRC5, XatiX |
|
|
|
|
|
#16 | |
|
Senior Member
Iscritto dal: Dec 2000
Messaggi: 501
|
Quote:
Perfetto, grazie mille. Credo che hai sciolto anche l'ultimo dubbio (spero...), quindi per sicurezza faccio un controesempio riprendendo il tuo codice: Se ho 4 thread attivi, di cui per varie ragioni 3 sono arrivati al punto di invocare method1() ed uno tenta di invocare method3(), dovrebbe accadere che uno dei 3 thread acquisisce il lock ed esegue method1(), mentre gli altri due si bloccano e vanno in attesa. Il 4° thread, invece continua l'esecuzione in parallelo al 1° thread, quindi: thread1 --> esegue method1() thread2 --> bloccato thread3 --> bloccato thread4 --> esegue method3() Fin qui è giusto? E se sia method1() che method3() tentassero di scrivere field2? Non si genererebbe alcuna eccezione, ma soltanto una probabile alterazione del risultato desiderato? Esempio: method1(), tra le varie operazioni, esegue anche: field2++; method3(), tra le varie operazioni, esegue anche: field2*2; Al termine di entrambi i thread posso avere un risultato differente per field2... E' corretto il ragionamento? |
|
|
|
|
|
|
#17 | |||
|
Senior Member
Iscritto dal: Oct 2007
Città: Padova
Messaggi: 4131
|
Quote:
Quote:
Quote:
Se mastichi inglese prova a leggerti questo: "Java Tutorial, Synchronization" dovrebbe fugare eventuali dubbi resiudi.
__________________
As long as you are basically literate in programming, you should be able to express any logical relationship you understand. If you don’t understand a logical relationship, you can use the attempt to program it as a means to learn about it. (Chris Crawford) Ultima modifica di banryu79 : 12-01-2012 alle 18:02. |
|||
|
|
|
|
|
#18 |
|
Senior Member
Iscritto dal: Dec 2000
Messaggi: 501
|
Grazie mille, ora è tutto chiaro!
|
|
|
|
|
| Strumenti | |
|
|
Tutti gli orari sono GMT +1. Ora sono le: 17:55.




















