סדר התשובות בלולאה אסינכרונית
-
כמו שאמרו כאן כמה חברים שאני אצטרך די מהר להשתמש בקוד אסינכרוני
זה הגיע יותר מהר ממה שחשבתי
אני מנסה לבדוק איך הAPI שלי (שכתוב בPHP) מחזיק במספר קריאות במקביל (למה זה אמור בכלל לעשות בעיה , זה נושא בפני עצמו)
אז בניתי סקריפט בגו שישלח בקשה לשרת דרך HTTP
ועשיתי לולאה של קריאות אסינכרוניות (דהיינו שקורא לפונקציה ולא מחכה לתשובה ומפעיל את הפונקציה שוב )
עד עכשיו הכל טוב ויפה אבל לא תמיד הקריאה הראשונה מסתיימת לפני השנייה
ואני לא יודע מתוך התשובות שקיבלתי מה היה סדר הקריאות
ניסיתי לכתוב שידפיס בסוף הפונקציה את המספר של הלולאה הנוכחית
אבל תמיד זה יוצא לי בסדר הפוך (דהיינו תוצאה 2 ואז 1)
האם אני עשיתי נכון ופשוט משום מה הקריאה השניה חוזרת לפני הראשונה או שלא הבנתי נכון איך זה עובד?
אני יצרף פה את הקוד שאמנם כתוב בגו אבל אולי זה יעזור להבין את השאלהpackage main import ( "strings" "os" "fmt" "io/ioutil" "net/http" ) func postto(s int, c chan int) { body := strings.NewReader(`phone=0523456789&amount=3`) req, err := http.NewRequest("POST", "http://www.host.com/test.php", body) if err != nil { } resp, err := http.DefaultClient.Do(req) if err != nil { } defer resp.Body.Close() contents, err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Printf("%s", err) os.Exit(1) } fmt.Printf("%s\n", string(contents)) fmt.Println(s) c <- s } func main() { var b int = 3 var a int c := make(chan int) for a < b { a++ go postto(a,c) } x, y := <-c, <-c fmt.Println(x, y) }
-
עדכון:עכשיו הוספתי לתחילת הפונקציה
now := time.Now() unixNano := now.UnixNano() umillisec := unixNano / 1000000
שזה אמור להגדיר משתנה לזמן כרגע במילי שניות
ובסוף הפונקציה ליד איפה שהגדרתי שידפיס את מספר הלולאה
הגדרתי שידפיס את משתנה הזמן שהוגדר בתחילת הפונקציהfmt.Println("(correct)Millisecond : ", umillisec)
ותוצאה היא
OK 2 (correct)Millisecond : 1561995505989 OK 1 (correct)Millisecond : 1561995505991
OK זה התשובה מהשרת שלי
אבל למרבה הפלא בזמן כתוב קודם את הקריאה הראשונה ובמספר הלולאה את השניה
אז כנראה פיספסתי כאן משהו -
- אתה יכול להתפטר מהשורות המיותרות האלה
if err != nil { }
אם בהשמה תכתוב כך:
resp, _ := http.DefaultClient.Do(req)
במקום לכתוב err כותבים קו תחתון וזה פוטר אותך מלהשתמש במשתנה הזה.
- יש לך טעות בלולאה, אתה רצית 2 קריאות אבל אתה בעצם מריץ לולאה של 3 קריאות כי a מתחיל מאפס ונגמר ב 3, הסיבה שאתה לא רואה 3 תוצאות כנראה בגלל שהפונקציה main מתה לפני שהקריאה השלישית מתסיימת. תוסיף בסוף הפונקציה השהייה כזו:
time.Sleep(10 * time.Second)
ותבדוק אם זה משנה את התמונה
לגופה של שאלה, לא התעמקתי לגמרי בקוד שלך, אבל זה נשמע לי הגיוני לגמרי שקריאה שניה מגיעה לפני קריאה ראשונה, זה תלוי בהרבה פרמטרים, המשתנה של הזמן לא מעיד על שום דבר כי שני המשתנים מאותחלים כמעט במקביל כי שתי הפונקציות נקראות אחת אחרי השניה ברצף וזה מאותחל מיד עם אתחול הפונקציה
-
- הורדתי את השורות של הerr (זה לא הבעיה אבל הקוד יותר נקי)
- הלולאה רצה 2 פעמים כי התנאי הוא a<2 ולא a<=2
לצורך ביטחון הגדרתי Sleep ל10 שניות
ועדיין מחזיר רק שתי קריאות
לגופו של עניין אין לי בעיה שיחזרו בסדר שונה
אבל אני רוצה לדעת מה הסדר של הקריאותהמשתנה של זמן לכאורה אמור להצביע על איזה קריאה נעשתה קודם
כי יש הבדל של חלקיק שנייה בין קריאה לקריאה
וגם המשתנה a אמור להיות בלולאה הראשונה 1 ובשניה 2
אבל זה עובד בדיוק הפוך משום מהעכשיו הרצי את הקוד פעמים וכל פעם חזר בסדר שונה
אין לי בעיה עם זה אבל מה קרה בכל פעם
שים לב שגם המשתנים x y מושפעים מסדר התשובות (כי הם מקבלים את הערך מs)root@scw-vibrant-bhabha:~# go run /root/go/src/curl/main2.go OK 2 (correct)Millisecond : 1562049086784 OK 1 (correct)Millisecond : 1562049086787 2 1 root@scw-vibrant-bhabha:~# go run /root/go/src/curl/main2.go OK 1 (correct)Millisecond : 1562049526808 OK 2 (correct)Millisecond : 1562049526807 1 2
-
@nigun ברגע שאתה קורא לפונקציה ע"י מילת המפתח
go
, הפסדת כל שליטה על סדר הפעולות, ה-scheduler של גו חופשי לעשות מה שבא לו. אין לך זכות לצפות לשום סדר דטרמיניסטי.אתה כן יכול להניח שה-scheduler לא סתם משחק משחקים איתך בשביל הכיף... ואם תעמיק בקוד של ה-scheduler תוכל להבין מה קורה, אבל אסור לך לסמוך על סדר מסויים.
פעם עניתי על שאלה דומה, וניסיתי לנתח איך ה-scheduler מבצע תוכנה מסויימת עיין כאן. אולי התשובה שם יעזור לך להבין מה קורה פה.
(בלי להכיר כל כך את השפה, אני די בטוח שבמקרה שחייבים סדר מסויים יש מבנים בתוך השפה שתוכל להשתמש בהם כדי לסנכרן סדר הפעולות, אבל הרי לא עשית את זה)
-
עכשיו ניסיתי לוותר לגמרי על האזנה לערוץ
x, y := <-c, <-c fmt.Println(x, y)
ולהשתמש רק בפונקצית שינה
ועכשיו המספר לולאה כן לפי הסדר הזמנים של הקריאות
הנה התשובות שקיבלתי
הראשון זה בלי Sleep
והשני עם Sleep ובלי האזנה לערוץroot@scw-vibrant-bhabha:~# go run /root/go/src/curl/main2.go OK 1 (correct)Millisecond : 1562065593494 OK 2 (correct)Millisecond : 1562065593491 1 2 root@scw-vibrant-bhabha:~# go run /root/go/src/curl/main2.go OK 2 (correct)Millisecond : 1562065640343 OK 1 (correct)Millisecond : 1562065640341
זה נראה שבשני הפעמים הקריאה השניה חוזרת לפני הראשונה
אבל לפחות בפעם השניה כתוב לי שזה הקריאה השניה ולא הראשונה
וזה נראה נכון כי 1562065640343 זה אחרי 1562065640341
(אני מקווה שאני כותב ברור ולא קשקושים לא מובנים) -
@nigun לא הבנתי לְמֵה אתה חותר.
אם אתה רוצה להדפיס שורה ולדעת איזה פונקציה כותב את זה, תדפיס באותה שורה גם את המספר של הקריאה.
לגבי סדר הפעולות, אתה יכול לסמוך על זה שהסדר בתוך הפונקציה נשמר, אבל לא תוכל לסמוך על שום סדר בין-פונקציאלי.
אאל"ט, גם מילת המפתחgo
, לא מריץ את הפונקציה, אלא שם את הפונקציה בתור להרצה מתישהו. אתה יכול לסמוך שהקריאה הראשונה הושם בתור לפני השניה, אבל אתה לא יכול לסמוך על שסביבת ההרצה החליטה להתחיל את הרצת הראשונה לפני השניה. -
@yossiz
אני רוצה לדעת את מספר קריאה
ומשום מה זה לא נראה שזה כותב את המספר קריאה האמיתי
הדרך שאני מנסה לבדוק האם זה המספר קריאה הנכון
אני מדפיס בנוסף את זמן הקריאה
ומשום מה זה כותב קריאה 1 בשניה 343.. וקריאה 2 בשניה 341...
וכל זה קורה רק אם אני מחכה לתשובה מהערוץ בסוף הסקריפט
אבל אם אני סתם מחכה בסוף הסקריפט 10 שניות
אז יוצא לי קריאה 1 בשניה 341.. וקריאה 2 בשניה 343...
אם זה היה חד פעמי הייתי אומר שבדיוק ככה יצא שלפעמים הקריאה הראשונה מתחילה אחרי השניה
אבל זה כל הזמן חוזר בסדר הזה (גם אם הקריאה השניה חוזרת ראשונה וגם להיפך)אולי אם אני ישים את בשורה אחת זה יהיה יותר מובן
OK (correct)Millisecond : 1562077651112 at 1 OK (correct)Millisecond : 1562077651110 at 2
-
@nigun אמר בסדר התשובות בלולאה אסינכרונית:
ומשום מה זה לא נראה שזה כותב את המספר קריאה האמיתי
במושכל ראשון אין כל סיבה לחשוש שהפלט לא נכון.
הדרך שאני מנסה לבדוק האם זה המספר קריאה הנכון
אני מדפיס בנוסף את זמן הקריאהזו לא דרך נכונה לבדוק תקינות הפלט מכיון שא"א להניח שום הנחות בנוגע להתנהגותו של ה-scheduler בגו, ואתה מסתמך על ההנחה המוקדמת שפונקציה שביקשת את הרצתה ראשון באמת תרוץ ראשון, מה שכנראה תלוי בהמון משתנים שקשה להבינם בלי לקרוא את הקוד של ה-scheduler.
בכל מקרה, קוד שמסתמך על איזשהו התנהגות שהוא מצפה מה-scheduler הוא קוד לא נכון.אני יודע שלא עניתי על שאלתך "למה זה רץ בסדר זה?" אבל אופתע אם יש תשובה פשוטה לשאלה הזאת.
תשובה חלקית יכולה להיות שהחלטות ה-scheduler מושפעות מהצפי שלו לגבי איזה goroutine ישפיע על הזרימה החלקה של המשך הקוד. כאשר אתה קוראsleep
אז סביבת ההרצה "יודע" שאתה לא זמין לעוד הרבה זמן, וזה משפיע על החלטותיה לגבי על איזה ליבה ואיזה thread לתזמן את ה-goroutines שמחכים בתור. וזה משפיע על התזמון של ההרצות.יש לך פה בלוג ב-3 המשכים על נושא התזמון בגו.
https://www.ardanlabs.com/blog/2018/08/scheduling-in-go-part1.html
https://www.ardanlabs.com/blog/2018/08/scheduling-in-go-part2.html
https://www.ardanlabs.com/blog/2018/12/scheduling-in-go-part3.htmlאחרי זה, תוכל לקרוא את קוד המקור של רכיב ה-scheduler פה: https://golang.org/src/runtime/proc.go (להתראות בעוד חודש...)