รันไทม์คืออะไร Gosched ทำอะไร?


86

ในเวอร์ชันก่อนเปิดตัว go 1.5 ของเว็บไซต์ Tour of Goมีโค้ดส่วนหนึ่งที่มีลักษณะเช่นนี้

package main

import (
    "fmt"
    "runtime"
)

func say(s string) {
    for i := 0; i < 5; i++ {
        runtime.Gosched()
        fmt.Println(s)
    }
}

func main() {
    go say("world")
    say("hello")
}

ผลลัพธ์มีลักษณะดังนี้:

hello
world
hello
world
hello
world
hello
world
hello

สิ่งที่รบกวนใจคือเมื่อruntime.Gosched()ถูกลบโปรแกรมจะไม่พิมพ์คำว่า "world" อีกต่อไป

hello
hello
hello
hello
hello

เหตุผลที่เป็นเช่นนั้น? วิธีการที่ไม่runtime.Gosched()ส่งผลกระทบต่อการดำเนินการหรือไม่

คำตอบ:


143

บันทึก:

ตั้งแต่ Go 1.5 GOMAXPROCS ถูกตั้งค่าเป็นจำนวนคอร์ของฮาร์ดแวร์: golang.org/doc/go1.5#runtimeด้านล่างคำตอบเดิมก่อน 1.5


เมื่อคุณเรียกใช้โปรแกรม Go โดยไม่ได้ระบุตัวแปรสภาพแวดล้อม GOMAXPROCS Go goroutines จะถูกกำหนดเวลาสำหรับการดำเนินการในเธรด OS เดียว อย่างไรก็ตามเพื่อให้โปรแกรมดูเหมือนเป็นแบบมัลติเธรด (นั่นคือสิ่งที่ goroutines มีไว้เพื่อใช่หรือไม่) ตัวกำหนดตารางเวลา Go บางครั้งต้องเปลี่ยนบริบทการดำเนินการเพื่อให้ goroutine แต่ละตัวสามารถทำงานได้

ดังที่ฉันได้กล่าวไปแล้วเมื่อไม่ได้ระบุตัวแปร GOMAXPROCS รันไทม์ Go จะได้รับอนุญาตให้ใช้เธรดเดียวเท่านั้นดังนั้นจึงเป็นไปไม่ได้ที่จะสลับบริบทการดำเนินการในขณะที่ goroutine กำลังทำงานแบบเดิม ๆ เช่นการคำนวณหรือแม้แต่ IO (ซึ่งแมปกับฟังก์ชัน C ธรรมดา ). บริบทสามารถเปลี่ยนได้ก็ต่อเมื่อใช้ Go concurrency primitives เช่นเมื่อคุณเปิด chans หลายตัวหรือ (นี่คือกรณีของคุณ) เมื่อคุณบอกให้ตัวกำหนดตารางเวลาเปลี่ยนบริบทอย่างชัดเจน - นี่คือสิ่งที่runtime.Goschedมีไว้สำหรับ

ดังนั้นในระยะสั้นเมื่อบริบทการดำเนินการในหนึ่ง goroutine ถึงการGoschedโทรตัวกำหนดตารางเวลาจะได้รับคำสั่งให้เปลี่ยนการดำเนินการเป็น goroutine อื่น ในกรณีของคุณมีสอง goroutines หลัก (ซึ่งหมายถึงหัวข้อ 'หลัก' ของโปรแกรม) go sayและเพิ่มเติมที่คุณได้สร้างขึ้นด้วย หากคุณลบการGoschedโทรบริบทการดำเนินการจะไม่ถูกโอนจาก goroutine ตัวแรกไปยังตัวที่สองดังนั้นจึงไม่มี 'โลก' สำหรับคุณ เมื่อGoschedมีอยู่ตัวกำหนดตารางเวลาจะโอนการดำเนินการในการวนซ้ำแต่ละลูปจาก goroutine ตัวแรกไปยังครั้งที่สองและในทางกลับกันคุณจึงมีคำว่า "hello" และ "world" แทรกอยู่

FYI สิ่งนี้เรียกว่า 'การทำงานหลายอย่างแบบร่วมมือกัน': goroutines ต้องให้การควบคุมกับ goroutines อื่น ๆ อย่างชัดเจน แนวทางที่ใช้ในระบบปฏิบัติการร่วมสมัยส่วนใหญ่เรียกว่า 'preemptive multitasking': เธรดการดำเนินการไม่เกี่ยวข้องกับการถ่ายโอนการควบคุม ตัวกำหนดตารางเวลาจะสลับบริบทการดำเนินการอย่างโปร่งใสไปแทน วิธีการแบบร่วมมือมักใช้ในการใช้ 'เธรดสีเขียว' นั่นคือโครูทีนพร้อมกันเชิงตรรกะซึ่งไม่ได้แมปเธรด 1: 1 กับ OS - นี่คือวิธีการใช้รันไทม์ Go และ goroutines

อัปเดต

ฉันได้กล่าวถึงตัวแปรสภาพแวดล้อม GOMAXPROCS แต่ไม่ได้อธิบายว่ามันคืออะไร ถึงเวลาแก้ไขปัญหานี้

เมื่อตัวแปรนี้ถูกตั้งค่าเป็นจำนวนบวกNรันไทม์ Go จะสามารถสร้างNเธรดเนทีฟได้สูงสุดซึ่งเธรดสีเขียวทั้งหมดจะถูกกำหนดเวลา Native thread คือเธรดชนิดหนึ่งที่สร้างโดยระบบปฏิบัติการ (เธรด Windows, pthreads ฯลฯ ) ซึ่งหมายความว่าถ้าNมากกว่า 1 เป็นไปได้ว่า goroutines จะถูกกำหนดเวลาให้ดำเนินการในเธรดเนทีฟที่แตกต่างกันและดังนั้นจึงรันแบบขนาน (อย่างน้อยก็ขึ้นอยู่กับความสามารถของคอมพิวเตอร์ของคุณ: หากระบบของคุณใช้โปรเซสเซอร์มัลติคอร์ มีแนวโน้มว่าเธรดเหล่านี้จะขนานกันอย่างแท้จริงหากโปรเซสเซอร์ของคุณมีคอร์เดี่ยวการทำงานหลายอย่างล่วงหน้าที่นำมาใช้ในเธรด OS จะสร้างการมองเห็นการทำงานแบบขนาน)

เป็นไปได้ที่จะตั้งค่าตัวแปร GOMAXPROCS โดยใช้runtime.GOMAXPROCS()ฟังก์ชันแทนการตั้งค่าตัวแปรสภาพแวดล้อมล่วงหน้า ใช้สิ่งนี้ในโปรแกรมของคุณแทนปัจจุบันmain:

func main() {
    runtime.GOMAXPROCS(2)
    go say("world")
    say("hello")
}

ในกรณีนี้คุณสามารถสังเกตผลลัพธ์ที่น่าสนใจได้ เป็นไปได้ว่าคุณจะได้รับบรรทัด "สวัสดี" และ "โลก" ที่พิมพ์แทรกสลับกันอย่างไม่สม่ำเสมอเช่น

hello
hello
world
hello
world
world
...

สิ่งนี้สามารถเกิดขึ้นได้หาก goroutines ถูกกำหนดเวลาให้แยกเธรด OS นี่คือวิธีการทำงานของมัลติทาสก์แบบ preemptive (หรือการประมวลผลแบบขนานในกรณีของระบบมัลติคอร์): เธรดจะขนานกันและเอาต์พุตที่รวมกันนั้นไม่แน่นอน BTW คุณสามารถออกหรือลบการGoschedโทรได้ดูเหมือนว่าจะไม่มีผลเมื่อ GOMAXPROCS ใหญ่กว่า 1

ต่อไปนี้คือสิ่งที่ฉันได้รับจากการรันโปรแกรมหลายครั้งพร้อมการruntime.GOMAXPROCSโทร

hyperplex /tmp % go run test.go
hello
hello
hello
world
hello
world
hello
world
hyperplex /tmp % go run test.go
hello
world
hello
world
hello
world
hello
world
hello
world
hyperplex /tmp % go run test.go
hello
hello
hello
hello
hello
hyperplex /tmp % go run test.go
hello
world
hello
world
hello
world
hello
world
hello
world

ดูบางครั้งผลลัพธ์ก็สวยบางครั้งก็ไม่ได้ ความไม่แน่นอนในการดำเนินการ :)

การปรับปรุงอื่น ๆ

ดูเหมือนว่าในเวอร์ชันที่ใหม่กว่าของ Go compiler Go runtime จะบังคับให้ goroutines ไม่เพียงให้ผลตอบแทนจากการใช้งาน primitives พร้อมกันเท่านั้น แต่ยังรวมถึงการเรียกระบบ OS ด้วย ซึ่งหมายความว่าบริบทการดำเนินการสามารถสลับระหว่าง goroutines ได้เช่นกันในการเรียกฟังก์ชัน IO ดังนั้นในคอมไพเลอร์ Go ล่าสุดจึงเป็นไปได้ที่จะสังเกตพฤติกรรมที่ไม่แน่นอนแม้ว่า GOMAXPROCS จะไม่ได้ตั้งค่าหรือตั้งค่าเป็น 1 ก็ตาม


เยี่ยมมาก! แต่ฉันไม่พบปัญหานี้ภายใต้ go 1.0.3, wierd.
WoooHaaaa

1
นี่คือเรื่องจริง ฉันเพิ่งตรวจสอบสิ่งนี้ด้วย go 1.0.3 และใช่พฤติกรรมนี้ไม่ปรากฏ: แม้จะใช้ GOMAXPROCS == 1 โปรแกรมก็ทำงานราวกับว่า GOMAXPROCS> = 2 ดูเหมือนว่าใน 1.0.3 ตัวกำหนดตารางเวลาได้รับการปรับแต่งแล้ว
Vladimir Matveev

ฉันคิดว่ามีการเปลี่ยนแปลงคอมไพเลอร์ wrt go 1.4 ตัวอย่างในคำถาม OPs ดูเหมือนว่าจะสร้างเธรดระบบปฏิบัติการในขณะที่ (-> gobyexample.com/atomic-counters ) ดูเหมือนจะสร้างการตั้งเวลาแบบร่วมมือ โปรดอัปเดตคำตอบว่าเป็นความจริงหรือไม่
tez

8
ตั้งแต่ Go 1.5 GOMAXPROCS ถูกตั้งค่าเป็นจำนวนแกนของฮาร์ดแวร์: golang.org/doc/go1.5#runtime
thepanuto

1
@ paulkon Gosched()จำเป็นหรือไม่ขึ้นอยู่กับโปรแกรมของคุณไม่ได้ขึ้นอยู่กับGOMAXPROCSมูลค่า ประสิทธิภาพของการทำงานหลายอย่างพร้อมกันล่วงหน้ามากกว่าแบบร่วมมือนั้นขึ้นอยู่กับโปรแกรมของคุณด้วย หากโปรแกรมของคุณถูกผูกไว้กับ I / O การทำงานหลายอย่างร่วมกันกับ async I / O อาจจะมีประสิทธิภาพมากกว่า (เช่นมีปริมาณงานมากกว่า) มากกว่า I / O ที่อิงกับเธรด หากโปรแกรมของคุณมี CPU เป็นตัวเชื่อม (เช่นการคำนวณแบบยาว) การทำงานหลายอย่างแบบร่วมมือกันจะมีประโยชน์น้อยกว่ามาก
Vladimir Matveev

8

การตั้งเวลาสหกรณ์เป็นตัวการ โกรูทีนอื่น ๆ (พูดว่า "โลก") ตามกฎหมายอาจได้รับโอกาสเป็นศูนย์ในการดำเนินการก่อน / เมื่อหลักยุติซึ่งตามข้อกำหนดจะยุติ gorutines ทั้งหมด - กล่าวคือ กระบวนการทั้งหมด


1
โอเคให้runtime.Gosched()ผลตอบแทน นั่นหมายความว่าอย่างไร? มันให้การควบคุมกลับไปที่ฟังก์ชันหลัก?
Jason Yeo

5
ในกรณีเฉพาะนี้ใช่ โดยทั่วไปจะขอให้ผู้จัดกำหนดการเริ่มต้นและเรียกใช้ goroutines "พร้อม" ใด ๆ ตามลำดับการเลือกที่ไม่ได้ระบุโดยเจตนา
zzzz
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.