[java threads] producer/consumer: pointerprobleem

Pagina: 1
Acties:

  • JeroenTheStig
  • Registratie: Mei 2000
  • Laatst online: 02:25
Naar aanleiding van mijn vorig draadje hier op p&w ben ik bezig met een producer consumer probleem. Ik had nog niet eerder met het producer/consumer pattern gewerkt, maar ik heb hem nu zo goed als helemaal door.

Wat ik graag wil hebben is het volgende:

Ik heb een object Selector, met daarin een LinkedList en een object Job. Deze selector start twee threads op, namelijk een consumer en een producer, beide met een pointer naar de LinkedList en het object Job.

Voor de duidelijkheid post ik hier de code:

De Selector: De selector start de consumer en de producer op. Aan deze twee threads worden de Job en de JobListModel meegegeven.
Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Selector {

    private Job _currentJob;
    private JobListModel _jobListModel;

    public Selector() {
        _jobListModel = new JobListModel();
        _currentJob = new Job(99,9,9,9,9);

        Consumer c1 = new Consumer(_jobListModel,_currentJob);
        Producer p1 = new Producer(_jobListModel,_currentJob);

        c1.start();
        p1.start();
    }

     public static void main(String[] args) {
        new Selector();
    }
}


De bedoeling is dat de consumer de pointer Job vult met het eerste object uit de JobListModel. Dit object wordt daarbij verwijderd uit de JobListModel (queue).
Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Consumer extends Thread {
    private JobListModel _jobListModel;
    private Job _job;

    public Consumer(JobListModel jobListModel, Job job) {
        _jobListModel = jobListModel;
        _job = job;
    }

    public void run() {

        for (;;) {
            _job = _jobListModel.dequeue();
        }
    }
}


Wanneer de consumer zijn werk heeft gedaan, wordt ver volgens de consumer in werking gebracht. Deze zet de Job weer achteraan in de JobListModel (queue).
Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Producer extends Thread {
    private JobListModel _jobListModel;
    private Job _job;

    public Producer(JobListModel jobListModel, Job job) {
        _jobListModel = jobListModel;
        _job = job;
    }

    public void run() {
        for (;;) {
            _jobListModel.enqueue(_job);
            try {
                sleep((int)(Math.random() * 2000));
            } catch (InterruptedException e) { }
        }
    }
}


De JobListModel zal ik ook even posten, maar voor zover ik kan zien werkt deze naar behoren.

Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class JobListModel{
    private List<Job> _jobList;
    private boolean _available = true;

    public JobListModel() {
        _jobList = new LinkedList<Job>();
        _jobList.add(new Job(0,0,0,0,0));
        _jobList.add(new Job(1,10,6,1,5));
        _jobList.add(new Job(2,12,1,4,2));
    }

    public synchronized void enqueue(Job job) {

        if (_available == true) {
            try {
                wait();
            } catch (InterruptedException e) {}
        }
        _jobList.add(job);
        _available = true;
        System.out.println("enqueue: " + job);
        notifyAll();
    }

    public synchronized Job dequeue() {
        if (_available == false) {
            try {
                wait();
            } catch (InterruptedException e) {}
        }
        _available = false;
        notifyAll();
        System.out.println("dequeue: " + _jobList.get(0));
        return _jobList.remove(0);
    }
}


Wat gaat er fout?

In de Selector wordt een nieuwe instantie van Job aangemaakt. Deze wordt meegegeven aan de consumer en producer. De gemaakte instantie is niet interessant, de bedoeling is namelijk dat deze in de eerste stap direct wordt overschreven met het eerste object uit de JobListModel. Tenminste, dat zou je verwachten. Want bij de eerste producer-cycle wordt de lijst gevuld met Job 99, die ik in de selector had geinitialiseerd.

De pointer lijkt dus niet goed te gaan. Ik heb met debuggen gekeken of in de consumer thread de _job pointer wordt gevuld met het eerste object uit de lijst. Dit gaat goed. Maar zogauw de producer verder gaat met zijn werk, wordt de lijst niet met dit object gevuld.

Mijn vraag is of iemand ziet wat er verkeerd gaat. Naar mijn weten verandert er qua pointers helemaal niks bij het werken met threads, dus ik zie het even niet meer.

Ik zal hieronder de inhoud van de lijst weergeven na de eerste 3 cycles:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
na dequeue: job 0: priority 0;0 cpu cycles and 0 hdd cycles left.
item 0: job 1: priority 10;6 cpu cycles and 1 hdd cycles left.
item 1: job 2: priority 12;1 cpu cycles and 4 hdd cycles left.

na enqueue: job 99: priority 9;9 cpu cycles and 9 hdd cycles left.
item 0: job 1: priority 10;6 cpu cycles and 1 hdd cycles left.
item 1: job 2: priority 12;1 cpu cycles and 4 hdd cycles left.
item 2: job 99: priority 9;9 cpu cycles and 9 hdd cycles left.

na dequeue: job 1: priority 10;6 cpu cycles and 1 hdd cycles left.
item 0: job 2: priority 12;1 cpu cycles and 4 hdd cycles left.
item 1: job 99: priority 9;9 cpu cycles and 9 hdd cycles left.

na enqueue: job 99: priority 9;9 cpu cycles and 9 hdd cycles left.
item 0: job 2: priority 12;1 cpu cycles and 4 hdd cycles left.
item 1: job 99: priority 9;9 cpu cycles and 9 hdd cycles left.
item 2: job 99: priority 9;9 cpu cycles and 9 hdd cycles left.

na dequeue: job 2: priority 12;1 cpu cycles and 4 hdd cycles left.
item 0: job 99: priority 9;9 cpu cycles and 9 hdd cycles left.
item 1: job 99: priority 9;9 cpu cycles and 9 hdd cycles left.

na enqueue: job 99: priority 9;9 cpu cycles and 9 hdd cycles left.
item 0: job 99: priority 9;9 cpu cycles and 9 hdd cycles left.
item 1: job 99: priority 9;9 cpu cycles and 9 hdd cycles left.
item 2: job 99: priority 9;9 cpu cycles and 9 hdd cycles left.


Zoals je ziet wordt bij de eerste enqueue niet job 0, maar job 99 terug geplaatst. Uiteindelijk bestaat de lijst alleen uit job 99 objecten.

Ik begrijp niet wat er fout gaat, ik ben de code verschillende malen stap voor stap bij langs gegaan, maar je zou zeggen dat die pointers wel kloppen. Wie kan mij helpen?

  • Robtimus
  • Registratie: November 2002
  • Laatst online: 20:53

Robtimus

me Robtimus no like you

in de Selector maak je job 99 aan, en die geef je mee aan je Producer. En wat doet die Producer vervolgens? Die stopt elke keer weer diezelfde job 99 in de list.

Aangezien de Consumer ondertussen de andere jobs verwijdert zal je list na een tijd idd alleen maar job 99 bevatten, een aantal keer.

More than meets the eye
There is no I in TEAM... but there is ME
system specs


  • Woy
  • Registratie: April 2000
  • Niet online

Woy

Moderator Devschuur®
in je Producer gaat het verkeerd. Je var _job wordt daar nooit gewijzigd en je plaatst dus telkens dezelfde _job in de Queue.

edit:

Toch voortaan eerst even refreshen voordat ik reageer

[ Voor 23% gewijzigd door Woy op 02-09-2004 10:21 ]

“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”


  • JeroenTheStig
  • Registratie: Mei 2000
  • Laatst online: 02:25
IceManX schreef op 02 september 2004 @ 10:19:
in de Selector maak je job 99 aan, en die geef je mee aan je Producer. En wat doet die Producer vervolgens? Die stopt elke keer weer diezelfde job 99 in de list.

Aangezien de Consumer ondertussen de andere jobs verwijdert zal je list na een tijd idd alleen maar job 99 bevatten, een aantal keer.
De volgorde van afhandeling is in dit geval Consumer - Producer - Consumer - enz. Dus de Consumer zal er als eerst voor zorgen dat de pointer _job wordt gevuld met het eerste object uit de joblist. Job 99 is als het goed is op dat moment 'verloren'. Maar helaas is dit dus net het probleem.

  • Woy
  • Registratie: April 2000
  • Niet online

Woy

Moderator Devschuur®
Boktor schreef op 02 september 2004 @ 10:22:
[...]


De volgorde van afhandeling is in dit geval Consumer - Producer - Consumer - enz. Dus de Consumer zal er als eerst voor zorgen dat de pointer _job wordt gevuld met het eerste object uit de joblist. Job 99 is als het goed is op dat moment 'verloren'. Maar helaas is dit dus net het probleem.
Ja maar je Consumer veranderd de inhoud van _job in Producer niet. Dat ze beide een reference naar hetzelfde object hebben zegt nog niet dat als je in Consumer die reference naar een ander object zet dat deze in Producer ook veranderd.

“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”


  • JeroenTheStig
  • Registratie: Mei 2000
  • Laatst online: 02:25
rwb schreef op 02 september 2004 @ 10:21:
in je Producer gaat het verkeerd. Je var _job wordt daar nooit gewijzigd en je plaatst dus telkens dezelfde _job in de Queue.

edit:

Toch voortaan eerst even refreshen voordat ik reageer
Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Producer extends Thread {
    private JobListModel _jobListModel;
    private Job _job;

    public Producer(JobListModel jobListModel, Job job) {
        _jobListModel = jobListModel;       // jobListModel en job worden vanuit de Selector
        _job = job;                         // meegegeven, dus _job zal vanaf nu naar de 
                                            // job in de selector pointen
    }

    public void run() {
        for (;;) {
            System.out.println("");
            _jobListModel.enqueue(_job);    // Hier zal de _job pointer aan de jobList worden
                                            // toegevoegd. Deze pointer wordt als het goed is
                                            // dus ook gewijzigd door de consumer. 
            try {
                sleep((int)(Math.random() * 2000));
            } catch (InterruptedException e) { }
            System.out.println("");
        }
    }
}


Ik kan dus niks fouts ontdekken hier
rwb schreef op 02 september 2004 @ 10:23:
[...]

Ja maar je Consumer veranderd de inhoud van _job in Producer niet. Dat ze beide een reference naar hetzelfde object hebben zegt nog niet dat als je in Consumer die reference naar een ander object zet dat deze in Producer ook veranderd.
Oke, dan maak ik dus een denkfout. Heeft dat te maken met het feit dat deze pointer in verschillende threads wordt gebruikt?

[ Voor 16% gewijzigd door JeroenTheStig op 02-09-2004 10:28 ]


  • Woy
  • Registratie: April 2000
  • Niet online

Woy

Moderator Devschuur®
Nee het heeft meer te maken dat je 2 verschillende references hebt. Ik denk dat je je een beetje vergist met C ofzo.

In de Selector maak je een Reference naar een Job object. Je Reference is intern inderdaad een pointer die ergens in het geheugen staat op het moment dat je die Reference doorgeeft aan de Consumer en Producer worden er 2 kopien van de Pointer gemaakt, ze hebben dus beide een aparte pointer die naar het zelfde object wijzen. Als je je pointer in de Consumer aanpast dan wijst de pointer in de Producer nog steeds naar het eerste object.

“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”


  • JeroenTheStig
  • Registratie: Mei 2000
  • Laatst online: 02:25
Ik heb nog even een klein testje geschreven, om te controleren hoe pointers zich gedragen. Check de volgende code:

Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class TestClass {

    private Objectje _stringObject = new Objectje("blaat");

    public static void main(String[] args) {
        new TestClass();
    }

    public TestClass() {
        Class1 class1 = new Class1(_stringObject);
        Class2 class2 = new Class2(_stringObject);

        class1.fillObject();
        class2.printObject();
    }
}


Java:
1
2
3
4
5
6
7
8
9
10
11
12
public class Class1 {

    private Objectje _stringObject;

    public Class1(Objectje stringObject) {
        _stringObject = stringObject;
    }

    public void fillObject() {
        _stringObject.setString("gewijzigd door class 1");
    }
}


Java:
1
2
3
4
5
6
7
8
9
10
11
12
public class Class2 {

    private Objectje _stringObject;

    public Class2(Objectje stringObject) {
        _stringObject = stringObject;
    }

    public void printObject() {
        System.out.println(_stringObject);
    }
}


Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Objectje {
    private String _string;

    public Objectje(String string) {
       _string = string;
    }

    public String toString() {
        return _string;
    }

    public void setString(String string) {
        _string = string;
    }
}


rwb, ik denk dat zoals jij het hebt uitgelegd, je de volgende output verwacht:
code:
1
blaat


Maar niets is minder waar, want wat krijg ik op m'n console te zien?
code:
1
gewijzigd door class 1


Zal er qua pointers toch een verschil zitten tussen een single thread en multi thread? Ik begrijp er nu helemaal niks meer van ...

[ Voor 190% gewijzigd door JeroenTheStig op 02-09-2004 10:57 ]


  • JeroenTheStig
  • Registratie: Mei 2000
  • Laatst online: 02:25
Oke, ik heb het met behulp van een getter en een setter in de selector het probleem opgelost. Maar ik zou nog wel graag willen weten hoe het nu precies zit met pointers in multi-threaded programma's.

  • Robtimus
  • Registratie: November 2002
  • Laatst online: 20:53

Robtimus

me Robtimus no like you

Het gaat in je voorbeeld goed omdat je 2 references hebt naar 1 object, en BINNEN IN dat object wordt een reference veranderd.

Probeer maar eens StringObject te vervangen door String, en dan gewoon in Class1 _string = "gewijzigd door class1" neerzetten.


Terug naar je producer en consumer. Ze krijgen op het begin allebei een reference (pointer) naar je ene job. Echter, deze pointers worden als value doorgegeven aan de methods! (de pointers wel, dus de objecten als reference)

Die pointer blijft in je producer altijd hetzelfde, wijst dus altijd naar je job 99.
In de consumer verander je de waarde van de pointer, die gaat naar een ander object wijzen. Het zijn echter 2 verschillende pointers (door dat als value doorgeven). De ene veranderen verandert de ander nog niet.

More than meets the eye
There is no I in TEAM... but there is ME
system specs


  • Robtimus
  • Registratie: November 2002
  • Laatst online: 20:53

Robtimus

me Robtimus no like you

Boktor schreef op 02 september 2004 @ 11:15:
Oke, ik heb het met behulp van een getter en een setter in de selector het probleem opgelost. Maar ik zou nog wel graag willen weten hoe het nu precies zit met pointers in multi-threaded programma's.
Precies hetzelfde. Alleen moet je nu ook synchroniseren op de shared objects (maar dat doe je al).

Pointers veranderen heus niet omdat je een extra thread hebt draaien. Je moet er alleen wat verstandiger mee omgaan.

More than meets the eye
There is no I in TEAM... but there is ME
system specs


  • JeroenTheStig
  • Registratie: Mei 2000
  • Laatst online: 02:25
edit:
reply over het hoofd gezien..


bedankt IceManX, ik zie nu wat er fout is gegaan.

Wat er dus verkeerd ging:

Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Consumer extends Thread {
    private JobListModel _jobListModel;
    private Job _job;

    public Consumer(JobListModel jobListModel, Job job) {
        _jobListModel = jobListModel;
        _job = job;
    }

    public void run() {

        for (;;) {
            _job = _jobListModel.dequeue();
        }
    }
}


_job = _jobListModel.dequeue();

_job krijgt hier dus een ander geheugenadres. Daar zat dus de fout.

[ Voor 203% gewijzigd door JeroenTheStig op 02-09-2004 11:26 ]


  • Robtimus
  • Registratie: November 2002
  • Laatst online: 20:53

Robtimus

me Robtimus no like you

Juist. En in je Producer blijft dat adres altijd hetzelfde.

More than meets the eye
There is no I in TEAM... but there is ME
system specs


  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024

Alarmnummer

-= Tja =-

Waarom heeft die consumer een job mee als argument? (En waarom heeft die een field _job? en waarom wordt dat field gezet in de run methode?)

  • Woy
  • Registratie: April 2000
  • Niet online

Woy

Moderator Devschuur®
Alarmnummer schreef op 02 september 2004 @ 12:14:
Waarom heeft die consumer een job mee als argument? (En waarom heeft die een field _job? en waarom wordt dat field gezet in de run methode?)
IDD. Dat was de opzet om zo de _job in Producer te veranderen. Maar dat werkt dus niet zo en daarom heeft _job in dit stukje code geen nut ( Mischien doe je er ergens anders nog wat mee ).

“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”


  • JeroenTheStig
  • Registratie: Mei 2000
  • Laatst online: 02:25
Alarmnummer schreef op 02 september 2004 @ 12:14:
Waarom heeft die consumer een job mee als argument? (En waarom heeft die een field _job? en waarom wordt dat field gezet in de run methode?)
Dat was het resultaat van het ombouwen van een standaard tutorial, maar dit veld is idd nutteloos nu.

  • Verbal Kint
  • Registratie: Januari 2001
  • Laatst online: 27-05-2025

Verbal Kint

The man with the plan

ik zou je aanraden om geen "unqualified access" te doen. Oftewel, als je attribbuten set of er naar verwijst zet er dan this. voor. Dan zie je ook sneller dat iets bijvoorbeeld een attribuut is en geen variabele. Ik weet niet welke IDE je gebruikt, maar meestal kun je het wel zo instellen dat je warnings krijgt in een dergelijk geval.

Great minds think alike!


  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024

Alarmnummer

-= Tja =-

Verbal Kint schreef op 02 september 2004 @ 14:41:
ik zou je aanraden om geen "unqualified access" te doen. Oftewel, als je attribbuten set of er naar verwijst zet er dan this. voor.
Hij gebruikt een prefix om verschil tussen een lokale variable en tussen een class field te maken.
Dan zie je ook sneller dat iets bijvoorbeeld een attribuut is en geen variabele. Ik weet niet welke IDE je gebruikt, maar meestal kun je het wel zo instellen dat je warnings krijgt in een dergelijk geval.
Echt goeie ide`s kan je een prefix zelf bij opgeven. Idea kan dit bv.
Pagina: 1