Random alles andere als Random?

  • Heute ist bei mir irgendwie Tag der seltsamen Bugs...

    Da ich seither zwischen meinen Teil Programmen der Einfachheit halber nur per Textdateien kommuniziert habe und das nun endlich mal ändern möchte habe ich mich mal näher damit beschäftigt per Stdin/Stdout zu kommunizieren. Soweit so gut, funktioniert soweit ich das beurteilen kann auch alles. Dabei ist mir jedoch aufgefallen, dass alle unabgängig voneinander laufenden Prozesse exakt die gleichen random Werte produzieren. Hier mal die beiden Testscripte:


    Main.au3:

    Spoiler anzeigen
    [autoit]


    #include <Constants.au3>
    #include <array.au3>
    Global $thread[4]
    Global $line[4]

    [/autoit] [autoit][/autoit] [autoit]

    $thread[1] = Run(@ScriptDir & "\stdout3.exe 1","",@SW_HIDE,$STDIN_CHILD + $STDOUT_CHILD)
    $thread[2] = Run(@ScriptDir & "\stdout3.exe 2","",@SW_HIDE,$STDIN_CHILD + $STDOUT_CHILD)
    $thread[3] = Run(@ScriptDir & "\stdout3.exe 3","",@SW_HIDE,$STDIN_CHILD + $STDOUT_CHILD)
    ;_ArrayDisplay($thread)

    [/autoit] [autoit][/autoit] [autoit]

    ;MsgBox(0,"","wait a sec")
    Sleep(500)
    For $i=1 to 3
    $line[$i] = StdoutRead($thread[$i])
    if StringInStr($line[$i],"thread started") Then
    ConsoleWrite($line[$i])
    StdinWrite($thread[$i],"start work")
    EndIf
    Next

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    ;~ ConsoleWrite(@CRLF & "------------ WAITING SOME TIME ------------" & @CRLF & @CRLF)
    ;~ Sleep(15000)

    [/autoit] [autoit][/autoit] [autoit]

    $done=0
    do
    for $i = 1 to 3
    if $line[$i]=-1 then ContinueLoop
    $line[$i] = StdoutRead($thread[$i])
    if $line[$i] <> "" Then ConsoleWrite($line[$i])
    if StringInStr($line[$i],"work done") Then
    $done += 1
    $line[$i]=-1
    EndIf
    Next
    Until $done=3

    [/autoit] [autoit][/autoit] [autoit]

    ;MsgBox(0,"","worker scheint fertig zu sein...")
    For $i = 1 to 3
    StdinWrite($thread[$i],"exit")
    $line[$i]=""
    Next

    [/autoit] [autoit][/autoit] [autoit]

    $done = 0
    do
    For $i = 1 to 3
    if $line[$i]=-1 then ContinueLoop
    $line[$i] = StdoutRead($thread[$i])
    if $line[$i] <> "" Then ConsoleWrite($line[$i])
    if StringInStr($line[$i],"exited by master") Then
    $done += 1
    $line[$i]=-1
    EndIf
    Next
    Until $done =3
    ;MsgBox(0,"","thread ist nun beendet")

    [/autoit]

    Stdout3.au3 (muss kompiliert werden)

    Spoiler anzeigen
    [autoit]


    $threadident = $cmdline[1]

    [/autoit] [autoit][/autoit] [autoit]

    ConsoleWrite("thread started " & $threadident & @CRLF)

    [/autoit] [autoit][/autoit] [autoit]

    do
    Sleep(100)
    until StringInStr(ConsoleRead(),"start work")

    [/autoit] [autoit][/autoit] [autoit]

    worktodo()
    ConsoleWrite("work done " & $threadident & @CRLF)

    [/autoit] [autoit][/autoit] [autoit]

    do
    Sleep(100)
    until StringInStr(ConsoleRead(),"exit")

    [/autoit] [autoit][/autoit] [autoit]

    ;MsgBox(0,"aus dem thread","thread wurde vom master beendet")
    ConsoleWrite("exited by master " & $threadident & @CRLF)
    Sleep(1000)
    Exit

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    Func worktodo()
    Local $ergebnis=0
    for $i=1 to 10
    Sleep(1000)
    $ergebnis += $i* $ergebnis + random(1000,9999,1)
    ;$ergebnis += $i* $ergebnis + _random()
    consolewrite("I am working (" & $ergebnis & ")! " & $threadident & @CRLF)
    Next
    EndFunc

    [/autoit] [autoit][/autoit] [autoit]

    func _random()
    Local $random = 1
    $random = Random($threadident,$threadident*2000,1)
    Return $random
    EndFunc

    [/autoit]

    Benutzt man die von mir modifizierte Random Funktion, welche den eindeutigen Threadidentifier verwendet ergeben sich wirkliche random Werte, benutzt man nur die normale random Funktion erhält man dieses consolen output der main.au3:

    Spoiler anzeigen

    Auch hier die Frage an euch wie sowas sein kann...

    Einmal editiert, zuletzt von misterspeed (10. März 2013 um 17:24)

  • Hi,
    Interessant, aber wenn ich

    [autoit]

    SRandom($threadident)
    $ergebnis += $i* $ergebnis + random(1000,9999,1)

    [/autoit]

    einfüge, werden verschiedene "Randoms" erzeugt.

    /EDIT/ wohl doch nicht, oder ich hab nen Denkfehler^^

    [autoit]

    SRandom($threadident)
    $ergebnis = random(1000,9999,1)

    [/autoit]


    I am working (4924)! 2
    I am working (5493)! 3
    I am working (3922)! 1
    I am working (3922)! 1
    I am working (4924)! 2
    I am working (5493)! 3
    I am working (4924)! 2
    I am working (5493)! 3
    I am working (3922)! 1
    I am working (5493)! 3
    I am working (3922)! 1
    I am working (4924)! 2
    I am working (4924)! 2
    I am working (5493)! 3
    I am working (3922)! 1
    I am working (5493)! 3
    I am working (3922)! 1
    I am working (4924)! 2
    I am working (4924)! 2
    I am working (5493)! 3
    I am working (3922)! 1
    I am working (5493)! 3
    I am working (3922)! 1
    I am working (4924)! 2
    I am working (3922)! 1
    I am working (4924)! 2
    I am working (5493)! 3
    I am working (4924)! 2

  • Andy, du setzt jedes Mal die Seed neu.
    Daraus folgt zwangsläufig, dass die Zahlen immer gleich sind.

    Normalerweise wird die RandomSeed über die systemzeit gesetzt.
    Wenn alle Skripte innerhalb eines kleinen Zeitabschnitts (10ms im Regelfall) gestartet werden, dann haben auch alle die gleiche Seed.

    Um das zu verhindern kannst du ein Sleep(100) (oder weniger. habs jetzt nicht getestet) zwischen die Run Befehle im Mainskript setzen.

    Dann läufts !

    lg
    M

    //Edit: Geteset:
    Mit Sleep(10) dazwischen haben bei mir immer 2 Skripte die gleiche Seed.
    Mit Sleep(20) haben alle eine unterschiedliche.
    --> Die Seed wird in 10ms intervallen festgelegt.

  • Um das zu verhindern kannst du ein Sleep(100) (oder weniger. habs jetzt nicht getestet) zwischen die Run Befehle im Mainskript setzen.

    Dann läufts !

    lg
    M

    //Edit: Geteset:
    Mit Sleep(10) dazwischen haben bei mir immer 2 Skripte die gleiche Seed.
    Mit Sleep(20) haben alle eine unterschiedliche.
    --> Die Seed wird in 10ms intervallen festgelegt.

    Nope kann ich nicht ganz bestätigen, habe das sleep da eingefügt wo es sinnvollerweise sein sollte

    [autoit]


    StdinWrite($thread[$i],"start work")
    Sleep(800)

    [/autoit]

    Selbst bei 800ms erhalte ich teilweise noch 2 gleichlaufende Threads:

    Code
    thread started 1
    thread started 2
    thread started 3
    I am working (8693)! 1
    I am working (32438)! 1
    I am working (9753)! 2
    I am working (9753)! 3
  • Hab ich auch gerade bemerkt...
    Eben gings noch.^^

    Dann mach ein SRandom(@MSEC) an den Beginn deiner Skripte.
    Dann reichen die 20ms auf jeden Fall.

    lg
    M

  • Tatsache 20ms sleep in der main.au3 plus Srandom(@MSEC) in der worktodo() Funktion scheinen das Problem zu lösen.

    Nungut ein wirkliches Problem war es nie, hatte mich nur interessiert, weil ich ursprünglich die threadident Variable per Random setzen wollte und dann in der Ausgabe lauter identische Thread Antworten hatte. Dachte zuerst in der main.au3 stimmt was nicht, deswegen hab ich das dann mal zu obigem Script umgebaut um festzustellen, dass die random Funktion nicht das macht was sie eigentlich machen sollte.

    Letzten Endes aber für meine eigentlichen Scripte nicht relevant, da handelt es sich um sehr unterschiedliche Teilprogramme die auch alles andere als synchron laufen. Das Testscript sollte mir nur helfen stdin und stdout besser zu verstehen.

    Trotzdem gut zu wissen, dass man sich auf die Zufälligkeit der random Funktion nicht immer verlassen kann.

  • Ich nehm das mal zurück, auch bei 20ms sleep plus srandom in den threads kann es noch zu identischen random Ergebnissen kommen. Etwa bei jedem 3. bis 4. Durchlauf

    Code
    thread started 1
    thread started 2
    thread started 3
    I am working (7331)! 2
    I am working (7331)! 1
    I am working (3378)! 3
  • Ist der SRandom Befehl eigentlich neu ?
    Ich kannte den garnicht...

    Hab frühere Skripte (vorallem sowas wie die ganzen Perlinberechnungen und Wellen für Towerdefense Spiele) immer mit eigenen Randoms versehen bei denen ich die Seed setzen konnte...
    Das wär ja auch mit SRandom gegangen, nur hab ich das nicht gefunden (oder nicht danach gesucht...)

    lg
    M

  • Hi,
    ja, sehr seltsam, ich bin bisher auch immer davon ausgegangen, daß die random()-Funktion aufgrund der Beschreibung in der Hilfe auch "unterschiedliche" Zahlen berechnet. Ein Parameter sollte definitiv der Counter für den Timer sein, der zählt die Takte seit dem Start des Rechners bzw Reset, ist offensichtlich nicht so... :thumbdown:

    Zitat von Hilfe zu Random()

    Übersetzte Kommentare basierend auf der Originalquelle

    Diese Funktion nutzt den"Mersenne Twister " Zufallszahlengenerator, MT19937, geschrieben von Takuji Nishimura, Makoto Matsumoto, Shawn Cokus, Matthe Bellew und Isaku Wada.

    Der "Mersenne Twister" ist ein Algorithmus für das Generieren von Zufallszahlen. Er wurde in Anbetracht der Mängel verschiedener anderer Generatoren entwickelt. Der Zyklus, 2<sup>19937-1, und der Verlauf der Gleichverteilung, 623 Dimensionen, sind hier weitaus größer. Ausserdem ist der Generator schnell. Er vermeidet Multiplikation und Division, und er profitiert von Cachespeichern und Pipelines. Weitere Informationen auf der Webseite der Entwickler unter http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html


    jedenfalls funktioniert nun

    [autoit]

    SRandom(timerinit())
    $ergebnis = random(1000,9999,1)

    [/autoit]
  • Hmm nö mit timerinit im srandom erhalte ich wieder 3 identische Ergebnisse, selbst dann noch wenn ich eine volle Sekunde Pause zwischen den thread starts habe.
    Ich verwende im übrigen noch die Autoit Version 3.3.6.1

    Code
    thread started 1
    thread started 2
    thread started 3
    I am working (3693)! 1
    I am working (3693)! 2
    I am working (3693)! 3

    EDIT:

    Vielleicht wurde ja eine sony Variante des random Algorithmus verwendet :D

    [Blockierte Grafik: http://i.imgur.com/pZ7Cz.jpg]

    Einmal editiert, zuletzt von misterspeed (29. Januar 2012 um 11:44)

  • Hi,
    timerinit() im srandom führt bei mir zu folgenden Ergebnissen:

  • Seltsam, vielleicht läuft dein System schon länger? Das seltsame ist auch, dass bei mir bei jeder erneuten Ausführung immer der Wert 3693 rauskommt wenn das srandom drin ist. Ist es auskommentiert kommen bei jedem Durchgang andere Werte, jedoch trotzdem identische Werte zu den anderen Threads heraus.

    Hab hier nochmal ein aktuelles Testscript, damit wir auch vom selben Script sprechen:


    main.au3

    Spoiler anzeigen
    [autoit]


    #include <Constants.au3>
    #include <array.au3>
    Global $thread[4]
    Global $line[4]

    [/autoit] [autoit][/autoit] [autoit]

    $thread[1] = Run(@ScriptDir & "\stdout3.exe 1","",@SW_HIDE,$STDIN_CHILD + $STDOUT_CHILD)
    $thread[2] = Run(@ScriptDir & "\stdout3.exe 2","",@SW_HIDE,$STDIN_CHILD + $STDOUT_CHILD)
    $thread[3] = Run(@ScriptDir & "\stdout3.exe 3","",@SW_HIDE,$STDIN_CHILD + $STDOUT_CHILD)

    [/autoit] [autoit][/autoit] [autoit]

    Sleep(500) ; needed to make sure all threads are ready

    [/autoit] [autoit][/autoit] [autoit]

    For $i=1 to 3
    $line[$i] = StdoutRead($thread[$i])
    if StringInStr($line[$i],"thread started") Then
    ConsoleWrite($line[$i])
    StdinWrite($thread[$i],"start work")
    Sleep(300) ; <--------- makes threads running timeshifted
    EndIf
    Next

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    ;~ ConsoleWrite(@CRLF & "------------ WAITING SOME TIME ------------" & @CRLF & @CRLF)
    ;~ Sleep(15000)

    [/autoit] [autoit][/autoit] [autoit]

    $done=0
    do
    for $i = 1 to 3
    if $line[$i]=-1 then ContinueLoop
    $line[$i] = StdoutRead($thread[$i])
    if $line[$i] <> "" Then ConsoleWrite($line[$i])
    if StringInStr($line[$i],"work done") Then
    $done += 1
    $line[$i]=-1
    EndIf
    Next
    Until $done=3

    [/autoit] [autoit][/autoit] [autoit]

    For $i = 1 to 3
    StdinWrite($thread[$i],"exit")
    $line[$i]=""
    Next

    [/autoit] [autoit][/autoit] [autoit]

    $done = 0
    do
    For $i = 1 to 3
    if $line[$i]=-1 then ContinueLoop
    $line[$i] = StdoutRead($thread[$i])
    if $line[$i] <> "" Then ConsoleWrite($line[$i])
    if StringInStr($line[$i],"exited by master") Then
    $done += 1
    $line[$i]=-1
    EndIf
    Next
    Until $done =3

    [/autoit]

    stdout3.au3

    Spoiler anzeigen
    [autoit]


    $threadident = $cmdline[1]

    [/autoit] [autoit][/autoit] [autoit]

    ConsoleWrite("thread started " & $threadident & @CRLF)

    [/autoit] [autoit][/autoit] [autoit]

    do
    Sleep(100)
    until StringInStr(ConsoleRead(),"start work")

    [/autoit] [autoit][/autoit] [autoit]

    worktodo()
    ConsoleWrite("work done " & $threadident & @CRLF)

    [/autoit] [autoit][/autoit] [autoit]

    do
    Sleep(100)
    until StringInStr(ConsoleRead(),"exit")

    [/autoit] [autoit][/autoit] [autoit]

    ConsoleWrite("exited by master " & $threadident & @CRLF)
    Sleep(1000)
    Exit

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    Func worktodo()
    $test=timerinit()
    SRandom($test)
    Local $ergebnis=0
    for $i=1 to 10
    Sleep(1000)
    $ergebnis += $i* $ergebnis + random(1000,9999,1)
    ;$ergebnis += $i* $ergebnis + _random() ; resolves issue
    consolewrite("I am working (" & $ergebnis & ")! " & $threadident & " Timerinitvalue: " & $test & @CRLF)
    Next
    EndFunc

    [/autoit] [autoit][/autoit] [autoit]

    func _random()
    Local $random = 1
    $random = Random($threadident,$threadident*2000,1)
    Return $random
    EndFunc

    [/autoit]

    Consolen Ausgabe:

    Code
    thread started 1
    thread started 2
    thread started 3
    I am working (3693)! 1 Timerinitvalue: 222403695098
    I am working (3693)! 2 Timerinitvalue: 222406602158
    I am working (3693)! 3 Timerinitvalue: 222410897938
  • Ich würde mal so etwas versuchen:

    [autoit]

    SRandom(@MSEC + @MSEC*@AutoItPId)

    [/autoit]

    PS: TimerInit funktioniert nicht, weil der Wertebereich zu groß ist.

    [autoit]

    SRandom(BitAND(TimerInit(), 0xFFFFFFFF))

    [/autoit]