Golang:如何超时信号量?

时间:2013-10-29 00:03:20

标签: multithreading concurrency thread-safety go semaphore

Golang中的信号量通过频道实现:

一个例子是: https://sites.google.com/site/gopatterns/concurrency/semaphores

上下文

我们有几百台服务器,并且我们希望限制访问的共享资源。因此,对于给定的资源,我们希望使用信号量来限制对这些服务器的仅5个并发访问的访问。为此,我们计划使用锁服务器。当机器访问资源时,它将首先向锁定服务器注册它正在通过密钥访问资源。然后当它完成时,它会向锁定服务器发送另一个请求,说它已完成并释放信号量。这可确保我们将对这些资源的访问限制为最大数量的并发访问。

问题:如果出现问题,请优雅地处理此问题。

问题

如何在信号量上实现超时?

示例:

让我说我的信号量大小为5.同时有10个进程尝试获取信号量中的锁,因此在这种情况下只有5个将获取它。

有时,进程会在没有响应的情况下死亡(真正的原因有点难以解释,但基本上有时候进程可能无法解锁)因此导致问题,因为信号量中的空间现在被永久锁定。

所以我想暂停一下。以下是一些问题:

流程将在2秒到60分钟之间的任何时间运行。

我们有一些竞争条件,因为如果它超时然后进程试图解锁它,那么我们已经解锁了两次信号量而不是一次。反之亦然,我们首先解锁它然后超时。

如何使用上面发布的建议模式并将其转换为具有超时的线程安全信号量?

5 个答案:

答案 0 :(得分:1)

要弄清楚你想要完成什么有点困难,但从我所知道的,你正在尝试让并发的goroutine访问共享资源,如果事情进展不顺利,就会优雅地处理它。关于如何处理这个问题,我有几点建议。

1)使用同步包中的WaitGroup:http://golang.org/pkg/sync/#example_WaitGroup

使用此策略,您基本上在每次调用新goroutine之前添加到计数器并使用延迟确保从计数器中删除(因此,无论是超时还是由于其他原因返回,它仍将从计数器中删除)。然后使用wg.Wait()命令确保在返回所有go例程之前它不再继续。下面是一个示例:http://play.golang.org/p/wnm24TcBZg请注意,如果没有wg.Wait(),它将不会等到go main例程完成,然后从main返回并终止。

2)使用time.Ticker自动超时:http://golang.org/pkg/time/#Ticker

这种方法基本上会设置一个定时器,它将以设定的间隔触发。您可以使用此计时器来控制基于时间的事件。基本上这必须在for循环中运行,等待通道被勾选,如下例所示:http://play.golang.org/p/IHeqmiFBSS

同样,不完全确定你要完成的是什么,但是你可以考虑将这两种方法结合起来,这样如果你的过程超时并且处于一个循环中,那么自动收报机会抓住它并在一定的时间后返回调用defer wg.Done()函数,以便等待它的代码部分继续运行。希望这至少有点帮助。

答案 1 :(得分:1)

由于您正在制作分布式锁服务,我假设您的锁服务器侦听端口,并且当您接受()您循环的连接时,等待每个连接的goroutine中的命令。当套接字被丢弃时,goroutine退出(即:远程节点崩溃)

所以,假设这是真的,你可以做几件事。

1)创建一个深度与并发锁数相匹配的通道 2)当你锁定时,向频道发送一条消息(如果已满,它将被阻止) 3)解锁时,只需从频道中读取消息即可 4)你可以“推迟发布()”(如果你已经锁定,那么发布会消耗一条消息)

这是一个粗略的工作示例,除了套接字之外的所有内容。 希望这是有道理的。 http://play.golang.org/p/DLOX7m8m6q

package main

import "fmt"

import "time"

type Locker struct {
    ch chan int
    locked bool
}

func (l *Locker) lock(){
    l.ch <- 1
    l.locked=true
}
func (l *Locker) unlock() {
    if l.locked { // called directly or via defer, make sure we don't unlock if we don't have the lock
        l.locked = false // avoid unlocking twice if socket crashes after unlock
        <- l.ch
    }
}

func dostuff(name string, locker Locker) {
    locker.lock()
    defer locker.unlock()
    fmt.Println(name,"Doing stuff")
    time.Sleep(1 * time.Second)
}

func main() {
    ch := make(chan int, 2)
    go dostuff("1",Locker{ch,false})
    go dostuff("2",Locker{ch,false})
    go dostuff("3",Locker{ch,false})
    go dostuff("4",Locker{ch,false})
    time.Sleep(4 * time.Second)
}

答案 2 :(得分:1)

ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
sem := semaphore.NewWeighted(int64(10))

if err := sem.Acquire(ctx, 1); err != nil {
 // - error means timeout else lock
}

答案 3 :(得分:0)

一些假设:

  • 您需要围绕 5台服务器一次通过锁服务器。
  • 访问该资源的时间很短,而且长度相似。

使用配额服务器而不是锁服务器。以平均(平均,75等)访问/锁定时间的5倍补充配额(一个简单的计数器)。只有在小于最大值时才补充配额。平均而言,您将保持大约5个并发访问/锁定。

一些高级功能:

  • 如果共享资源可以检测到它自己的负载,它可以告诉配额服务器它可以采取更多或更少的并发访问。
  • 服务器可以在配额服务器完成后ping通配额服务器。这不是必需的,但可以更快地释放资源。

答案 4 :(得分:0)

也许这会有所帮助,但我认为这种实施过于庞大 我会很感激有关代码的任何建议。

package main

import (
   "fmt"
   "time"
   "math/rand"
   "strconv"
)

type Empty interface{}

type Semaphore struct {
    dur time.Duration
    ch  chan Empty
}

func NewSemaphore(max int, dur time.Duration) (sem *Semaphore) {
    sem = new(Semaphore)
    sem.dur = dur
    sem.ch = make(chan Empty, max)
    return
}

type Timeout struct{}

type Work struct{}

var null Empty
var timeout Timeout
var work Work

var global = time.Now()

func (sem *Semaphore) StartJob(id int, job func()) {
    sem.ch <- null
    go func() {
        ch := make(chan interface{})
        go func() {
            time.Sleep(sem.dur)
            ch <- timeout
        }()
        go func() {
            fmt.Println("Job ", strconv.Itoa(id), " is started", time.Since(global))
            job()
            ch <- work
        }()
        switch (<-ch).(type) {
        case Timeout:
            fmt.Println("Timeout for job ", strconv.Itoa(id), time.Since(global))
        case Work:
            fmt.Println("Job ", strconv.Itoa(id), " is finished", time.Since(global))
        }
        <-sem.ch
    }()
}

func main() {
    rand.Seed(time.Now().Unix())
    sem := NewSemaphore(3, 3*time.Second)
    for i := 0; i < 10; i++ {
        id := i
        go sem.StartJob(i, func() {
            seconds := 2 + rand.Intn(5)
            fmt.Println("For job ", strconv.Itoa(id), " was allocated ", seconds, " secs")
            time.Sleep(time.Duration(seconds) * time.Second)
        })
    }
    time.Sleep(30 * time.Second)
}