Mencoba mendapatkan kunci dengan tenggat waktu di golang?

Bagaimana seseorang hanya bisa mencoba mendapatkan kunci seperti mutex di go, dengan segera membatalkannya (seperti yang dilakukan TryLock di implementasi lain) atau dengan mematuhi beberapa bentuk tenggat waktu (pada dasarnya LockBefore )?

Saya dapat memikirkan 2 situasi saat ini di mana hal ini akan sangat membantu dan di mana saya sedang mencari solusi. Yang pertama adalah: layanan dengan banyak CPU yang menerima permintaan sensitif latensi (misalnya layanan web). Dalam hal ini Anda ingin melakukan sesuatu seperti contoh RPCService di bawah. Dimungkinkan untuk mengimplementasikannya sebagai antrian pekerja (dengan saluran dan lainnya), namun dalam kasus ini akan menjadi lebih sulit untuk mengukur dan memanfaatkan semua CPU yang tersedia. Kita juga bisa menerima bahwa pada saat Anda memperoleh kunci, kode Anda mungkin sudah melewati tenggat waktu, namun hal ini tidak ideal karena akan menghabiskan sejumlah sumber daya dan berarti kita tidak dapat melakukan hal-hal seperti "pengaturan ad-hoc yang terdegradasi." tanggapan".

    /* 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]
      }
    }

Kasus lainnya adalah ketika Anda memiliki sekumpulan objek yang harus disentuh, tetapi mungkin terkunci, dan menyentuhnya harus selesai dalam jangka waktu tertentu (misalnya memperbarui beberapa statistik). Dalam hal ini Anda juga dapat menggunakan LockBefore() atau beberapa bentuk TryLock(), lihat contoh Statistik di bawah.

    /* 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)
    }

Untuk kemudahan penggunaan, mari kita asumsikan bahwa dalam kasus di atas kita sedang membicarakan tentang s.someObj yang sama. Objek apa pun mungkin diblokir oleh operasi DoTheThing() untuk waktu yang lama, yang berarti kita ingin melewatkannya di updateObjStats. Selain itu, kami ingin memastikan bahwa kami mengembalikan respons murah di DoTheThing() jika kami tidak dapat memperoleh kunci waktu.

Sayangnya, sync.Mutex hanya dan secara eksklusif memiliki fungsi Lock() dan Unlock(). Tidak ada cara untuk berpotensi mendapatkan kunci. Apakah ada cara mudah untuk melakukan ini? Apakah saya mendekati permasalahan ini dari sudut pandang yang salah, dan adakah cara lain yang lebih "jalan" untuk menyelesaikannya? Atau apakah saya harus mengimplementasikan perpustakaan Mutex saya sendiri jika saya ingin menyelesaikannya? Saya mengetahui masalah 6123 yang sepertinya menunjukkan bahwa tidak ada hal seperti itu dan bahwa cara saya menangani masalah ini sama sekali tidak masuk akal.


person A Child of The Universe    schedule 01.02.2019    source sumber
comment
Juga dibahas di sini: groups.google.com/forum/#! msg/golang-nuts/MTaJNZ49u60/ dan grup .google.com/forum/#!msg/golang-nuts/OM37bQ41n3Q/   -  person Eli Bendersky    schedule 02.02.2019


Jawaban (3)


Saya pikir Anda menanyakan beberapa hal berbeda di sini:

  1. Apakah fasilitas ini ada di perpustakaan standar? Tidak, tidak. Anda mungkin dapat menemukan implementasi di tempat lain - implementasi ini dapat dilakukan menggunakan pustaka standar (atom, misalnya).

  2. Mengapa fasilitas ini tidak ada di perpustakaan standar: masalah yang Anda sebutkan dalam pertanyaan adalah satu diskusi. Ada juga beberapa diskusi di milis go-nuts dengan beberapa pengembang kode Go yang berkontribusi: tautan 1, tautan 2. Dan diskusi lainnya mudah dicari dengan googling.

  3. Bagaimana saya bisa merancang program saya sedemikian rupa sehingga saya tidak memerlukannya?

Jawaban untuk (3) lebih bernuansa dan bergantung pada masalah sebenarnya Anda. Pertanyaan Anda sudah mengatakan

Dimungkinkan untuk mengimplementasikannya sebagai antrian pekerja (dengan saluran dan lainnya), namun dalam kasus ini akan menjadi lebih sulit untuk mengukur dan memanfaatkan semua CPU yang tersedia

Tanpa memberikan rincian mengapa akan lebih sulit untuk menggunakan semua CPU, dibandingkan memeriksa status kunci mutex.

Di Go Anda biasanya menginginkan saluran setiap kali skema penguncian menjadi tidak sepele. Seharusnya tidak lebih lambat, dan harus lebih mudah dipelihara.

person Eli Bendersky    schedule 01.02.2019

Gunakan saluran dengan ukuran buffer satu sebagai mutex.

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

Kunci:

l <- struct{}{}

Membuka kunci:

<-l

Coba kunci:

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

Coba dengan batas waktu:

select {
case l <- struct{}{}:
    // lock acquired
    <-l
case <-time.After(time.Minute):
    // lock not acquired
}
person Cerise Limón    schedule 01.02.2019
comment
Terima kasih, ini adalah solusi yang berhasil! Namun, keluhan saya adalah saya memakan 104 byte pada mesin 64-bit untuk chan ini (96 byte untuk objek hchan dan 8 untuk penunjuk ke sana). Itu belum termasuk alokasi untuk hchan.buf, yang saya harap hasilnya nihil. Saya akan mencoba sinkronisasi/atomik CompareAndSwapUint32. - person Daniel Santos; 29.06.2021

Bagaimana dengan paket ini: https://github.com/viney-shih/go-lock . Ini menggunakan saluran dan semaphore (golang.org/x/sync/semaphore) untuk menyelesaikan masalah Anda.

go-lock mengimplementasikan fungsi TryLock, TryLockWithTimeout dan TryLockWithContext selain Mengunci dan Membuka Kunci. Ini memberikan fleksibilitas untuk mengontrol sumber daya.

Contoh:

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