Is it a good practice to lock some data outside if-else and unlock inside if-else

huangapple go评论81阅读模式
英文:

Is it a good practice to lock some data outside if-else and unlock inside if-else

问题

当我测试rf.state == candidate时,可能会出现竞争条件。所以在此之前,我会锁定结构体rf。我想确保rf.stateif条件测试中出现的时候,我可以使用它的最新值。

有没有其他习惯用法来在类似情况下锁定数据(if-else、switch...)?


编辑:
这是我其他一些代码。

...
rf.mu.Lock()
switch rf.state {
case follower:
        rf.mu.Unlock()

        select {
        case <-rf.chanAppends:
        case <-rf.chanGrantVotes:
        case <-time.After(time.Duration(500+rand.Intn(1000)) * time.Millisecond):
                rf.mu.Lock()
                rf.state = candidate
                rf.mu.Unlock()
        }
case candidate:
        rf.mu.Unlock()
        ...
case leader:
        rf.mu.Unlock()
        ...
}

为了在switch中使用当前的rf.state,我必须在外部进行锁定,并在内部进行解锁。让我困扰的是,对于每个case,我都必须解锁(使代码变得不够清晰和可读?)。而且在每个case中都有一些阻塞操作(例如time.After(...)),所以我看不到使用defer来帮助我的方法。我想这是一种权衡,如果我不想改变这个switch的逻辑结构的话。

英文:

When I test rf.state == candidate, there could be a race. So I lock the struct rf before that. I want to make sure rf.state appears exactly in the if condition test so I can use its most current value.

...
func (rf *Raft) runElection() {
	rf.mu.Lock()
	rf.currentTerm += 1
	rf.votedFor = rf.me
	rf.votes = 1
	rf.mu.Unlock()

	for server := range rf.peers {
		rf.mu.Lock()
		if server != rf.me &amp;&amp; rf.state == candidate {
			rf.mu.Unlock()
			go rf.sendRequestVote(server)
		} else {
			rf.mu.Unlock()
		}
	}
}

...

Is there any other idiomatic way to lock the data in similar situations (if-else, switch...)?


EDITS:
Here is some other code of mine.

...
rf.mu.Lock()
switch rf.state {
case follower:
        rf.mu.Unlock()

        select {
        case &lt;-rf.chanAppends:
        case &lt;-rf.chanGrantVotes:
        case &lt;-time.After(time.Duration(500+rand.Intn(1000)) * time.Millisecond):
                rf.mu.Lock()
                rf.state = candidate
                rf.mu.Unlock()
        }
case candidate:
        rf.mu.Unlock()
        ...
case leader:
        rf.mu.Unlock()
        ...

To use the current rf.state upon switch, I have to lock at the outside and unlock it at the inside. What bothers me is that for every case I have to unlock for every cases (making the code less clean and readable?). And there is some blocking (e.g. time.After(...)) going on to make a timer inside every case so I see no way of using defer to help me. I guess it's some tradeoff I have to make if I don't want to alter the logical structure of this switch?

答案1

得分: 1

你的代码看起来还不错,但是你可以将其中的部分提取到自己的函数中,以便使用defer

func (rf *Raft) runElection() {
    // ...

    for server := range rf.peers {
        tryVote(rf, server)
    }
}

func tryVote(rf *Raft, server xxx) {
    rf.mu.Lock()
    defer rf.mu.Unlock()
    if server != rf.me && rf.state == candidate {
        go rf.sendRequestVote(server)
    }
}

当你的代码变得更加复杂,并且你想要确保互斥锁在从某个代码段返回后绝对被解锁时,这种方法尤其有用。

英文:

While your code looks okay, you can always extract parts into their own functions to make use of defer.

func (rf *Raft) runElection() {
    // ...

    for server := range rf.peers {
        tryVote(rf, server)
    }
}

func tryVote(rf *Raft, server xxx) {
    rf.mu.Lock()
    defer rf.mu.Unlock()
    if server != rf.me &amp;&amp; rf.state == candidate {
        go rf.sendRequestVote(server)
    }
}

This is especially helpful when your code gets more complex and you want to make ABSOLUTELY sure the mutex gets unlocked once you return from some code section.

huangapple
  • 本文由 发表于 2022年2月15日 17:30:18
  • 转载请务必保留本文链接:https://go.coder-hub.com/71124017.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定