กำลังพยายามรับล็อคโดยมีกำหนดเวลาใน golang หรือไม่?

เราจะพยายามรับการล็อคแบบ mutex ได้อย่างไรไม่ว่าจะยกเลิกทันที (เหมือนที่ TryLock ทำในการใช้งานอื่น ๆ ) หรือโดยการสังเกตกำหนดเวลาบางรูปแบบ (โดยพื้นฐานแล้ว LockBefore )?

ตอนนี้ฉันนึกถึง 2 สถานการณ์ที่สิ่งนี้จะเป็นประโยชน์อย่างมาก และที่ฉันกำลังมองหาวิธีแก้ไข ประการแรกคือ: บริการที่เน้น CPU มากซึ่งรับคำขอที่มีความละเอียดอ่อนด้านเวลาแฝง (เช่น บริการบนเว็บ) ในกรณีนี้ คุณต้องการดำเนินการบางอย่างเหมือนกับตัวอย่าง RPCService ด้านล่าง เป็นไปได้ที่จะนำไปใช้เป็นคิวผู้ปฏิบัติงาน (พร้อมแชนเนลและสิ่งของ) แต่ในกรณีนี้ จะยากขึ้นในการวัดและใช้ CPU ที่มีอยู่ทั้งหมด นอกจากนี้ยังเป็นไปได้ที่จะยอมรับว่าเมื่อถึงเวลาที่คุณได้รับล็อค รหัสของคุณอาจเกินกำหนดเวลาแล้ว แต่นั่นไม่เหมาะเนื่องจากเป็นการสิ้นเปลืองทรัพยากรจำนวนหนึ่ง และหมายความว่าเราไม่สามารถทำสิ่งต่าง ๆ เช่น "เฉพาะกิจที่เสื่อมโทรมลง" การตอบสนอง".

    /* Example 1: LockBefore() for latency sensitive code. */
    func (s *RPCService) DoTheThing(ctx context.Context, ...) ... {
      if s.someObj[req.Parameter].mtx.LockBefore(ctx.Deadline()) {
        defer s.someObj[req.Parameter].mtx.Unlock()
        ... expensive computation based on internal state ...
      } else {
        return s.cheapCachedResponse[req.Parameter]
      }
    }

อีกกรณีหนึ่งคือเมื่อคุณมีวัตถุจำนวนมากที่ควรสัมผัส แต่อาจถูกล็อค และจุดที่การสัมผัสควรเสร็จสิ้นภายในระยะเวลาที่กำหนด (เช่น การอัปเดตสถิติบางอย่าง) ในกรณีนี้ คุณสามารถใช้ LockBefore() หรือ TryLock() บางรูปแบบได้ ดูตัวอย่างสถิติด้านล่าง

    /* Example 2: TryLock() for updating stats. */
    func (s *StatsObject) updateObjStats(key, value interface{}) {
      if s.someObj[key].TryLock() {
        defer s.someObj[key].Unlock()
        ... update stats ...
        ... fill in s.cheapCachedResponse ...
      }
    }

    func (s *StatsObject) UpdateStats() {
      s.someObj.Range(s.updateObjStats)
    }

เพื่อความสะดวกในการใช้งาน สมมติว่าในกรณีข้างต้น เรากำลังพูดถึง s.someObj เดียวกัน ออบเจ็กต์ใดๆ อาจถูกบล็อกโดยการดำเนินการ DoTheThing() เป็นเวลานาน ซึ่งหมายความว่าเราต้องการข้ามไปใน updateObjStats นอกจากนี้ เราต้องการให้แน่ใจว่าเราจะส่งคืนการตอบกลับราคาถูกใน DoTheThing() ในกรณีที่เราไม่สามารถรับการล็อคได้ทันเวลา

น่าเสียดายที่ sync.Mutex เท่านั้นและมีฟังก์ชัน Lock() และ Unlock() เท่านั้น ไม่มีทางที่จะอาจได้รับการล็อค มีวิธีง่ายๆ ในการทำเช่นนี้แทนหรือไม่? ฉันกำลังเผชิญกับปัญหาประเภทนี้จากมุมที่ผิดอย่างสิ้นเชิง และมีวิธีที่ "ไปสู่" ที่แตกต่างและมากกว่าในการแก้ปัญหาหรือไม่ หรือฉันจะต้องใช้ไลบรารี Mutex ของตัวเองหากต้องการแก้ไขปัญหาเหล่านี้ ฉันทราบถึง ฉบับที่ 6123 ซึ่งดูเหมือนว่าจะแนะนำว่าไม่มีสิ่งนั้นและ วิธีที่ฉันจัดการกับปัญหาเหล่านี้เป็นเรื่องที่ไม่เข้าท่าเลย


person A Child of The Universe    schedule 01.02.2019    source แหล่งที่มา
comment
พูดคุยกันที่นี่ด้วย: groups.google.com/forum/#! msg/golang-nuts/MTaJNZ49u60/ และ กลุ่ม .google.com/forum/#!msg/golang-nuts/OM37bQ41n3Q/   -  person Eli Bendersky    schedule 02.02.2019


คำตอบ (3)


ฉันคิดว่าคุณกำลังถามสิ่งต่าง ๆ หลายประการที่นี่:

  1. สิ่งอำนวยความสะดวกนี้มีอยู่ในไลบรารีมาตรฐานหรือไม่ ไม่มันไม่ได้ คุณอาจพบการใช้งานที่อื่น - เป็นไปได้ที่จะใช้งาน โดยใช้ ไลบรารีมาตรฐาน (เช่น อะตอมมิก)

  2. เหตุใดสิ่งอำนวยความสะดวกนี้จึงไม่มีอยู่ในห้องสมุดมาตรฐาน: ปัญหาที่คุณกล่าวถึงในคำถามคือการสนทนาครั้งหนึ่ง นอกจากนี้ยังมีการอภิปรายหลายครั้งเกี่ยวกับรายชื่ออีเมล go-nuts ที่มีนักพัฒนาโค้ด Go หลายคนมีส่วนร่วม: ลิงก์ 1, ลิงก์ 2. และง่ายต่อการค้นหาการสนทนาอื่น ๆ โดย Google

  3. ฉันจะออกแบบโปรแกรมของฉันโดยที่ไม่ต้องการสิ่งนี้ได้อย่างไร

คำตอบของ (3) มีความเหมาะสมยิ่งขึ้นและขึ้นอยู่กับปัญหาที่แท้จริงของคุณ คำถามของคุณบอกไปแล้ว

เป็นไปได้ที่จะนำไปใช้เป็นคิวผู้ปฏิบัติงาน (พร้อมแชนเนลและสิ่งของ) แต่ในกรณีนี้ มันจะยากขึ้นในการวัดและใช้ CPU ที่มีอยู่ทั้งหมด

โดยไม่ต้องให้รายละเอียดว่าทำไมการใช้ CPU ทั้งหมดจึงยากกว่า เมื่อเทียบกับการตรวจสอบสถานะการล็อก mutex

ใน Go คุณมักจะต้องการช่องเมื่อใดก็ตามที่รูปแบบการล็อคกลายเป็นเรื่องไม่สำคัญ ไม่ควรช้าลงและควรบำรุงรักษาได้มากกว่านี้มาก

person Eli Bendersky    schedule 01.02.2019

ใช้แชนเนลที่มีขนาดบัฟเฟอร์เท่ากับ 1 เป็น mutex

l := make(chan struct{}, 1)

ล็อค:

l <- struct{}{}

ปลดล็อค:

<-l

ลองล็อค:

select {
case l <- struct{}{}:
    // lock acquired
    <-l
default:
    // lock not acquired
}

ลองหมดเวลา:

select {
case l <- struct{}{}:
    // lock acquired
    <-l
case <-time.After(time.Minute):
    // lock not acquired
}
person Cerise Limón    schedule 01.02.2019
comment
ขอบคุณ นี่เป็นวิธีแก้ปัญหาที่ใช้งานได้! อย่างไรก็ตาม ข้อร้องเรียนของฉันคือฉันกำลังกิน 104 ไบต์บนเครื่อง 64 บิตสำหรับ chan นี้ (96 ไบต์สำหรับวัตถุ hchan และ 8 สำหรับตัวชี้) นั่นไม่รวมถึงการจัดสรรสำหรับ hchan.buf ซึ่งฉันหวังว่าจะเป็นตัวชี้ศูนย์ ฉันจะลองใช้ sync/atomic CompareAndSwapUint32 ดู - person Daniel Santos; 29.06.2021

แล้วแพ็คเกจนี้ล่ะ: https://github.com/viney-shih/go-lock . ใช้ ช่อง และ เซมาฟอร์ (golang.org/x/sync/semaphore) เพื่อแก้ไขปัญหาของคุณ

go-lock ใช้ฟังก์ชัน TryLock, TryLockWithTimeout และ TryLockWithContext นอกเหนือจากการล็อคและปลดล็อค มันให้ความยืดหยุ่นในการควบคุมทรัพยากร

ตัวอย่าง:

package main

import (
    "fmt"
    "time"
    "context"

    lock "github.com/viney-shih/go-lock"
)

func main() {
    casMut := lock.NewCASMutex()

    casMut.Lock()
    defer casMut.Unlock()

    // TryLock without blocking
    fmt.Println("Return", casMut.TryLock()) // Return false

    // TryLockWithTimeout without blocking
    fmt.Println("Return", casMut.TryLockWithTimeout(50*time.Millisecond)) // Return false

    // TryLockWithContext without blocking
    ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
    defer cancel()

    fmt.Println("Return", casMut.TryLockWithContext(ctx)) // Return false


    // Output:
    // Return false
    // Return false
    // Return false
}
person Viney    schedule 06.05.2020