Read configuration variables without lock

Get into the habit of writing together! This is the 4th day of my participation in the "Nuggets Daily New Plan·April Update Challenge", click to view the details of the event .

problem introduction

In general, a backend program will have many dynamic configurations, that is, variables that are not allowed to take effect on restart.

Why are these configuration variables changed and rebooting is not allowed?

This is of course because the cost of restarting once, for example, is that the service is stalled for at least this period of time. (Even if it is highly available, a bunch of services are deployed, and it should not be restarted if it can be restarted).

So there is a problem, these configuration variables are not read-only, but can change at any time according to demand. After the change, it should take effect.

The real service programs are multi-threaded, which is equivalent to having one write operation and multiple read operations at the same time.

Unlocked? Will it work?

forGolang an example

...
var MaxConn = 10000 // 最大连接数,允许10000
...
复制代码

Looking at the code above, we have a variable MaxConnthat limits the maximum number of connections this program can accept.

If this variable will not change after the program starts, then when reading this variable, just:

if (nowConn < MaxConn) {
    return true
}
复制代码

This code is allowed.

But once we say, that this variable is going to change while the program is running, then not.

var MaxConn = 10000
var MaxConnLock sync.Mutex // 这里要一把锁
复制代码
// 读取时
MaxConnLock.Lock()
defer MaxConnLock.Unlock()
if (nowConn < MaxConn) {
    return true
}
复制代码
// 写入时
MaxConnLock.Lock()
defer MaxConnLock.Unlock()
MaxConn = 20000
复制代码

It can be seen that it is indeed necessary to add a lock, and this way of writing is correct.

Solution 1: Atomic Operations

Golang provides many atomic operations on variables.

What is an atomic operation, that is, an operation that does not generate competition between multiple threads, and is a safe multi-threaded read and write operation.

E.g:

//  声明
var MaxConn int64 = 10000

// 读操作
maxConn = atomic.LoadInt64(&MaxConn)

// 写操作
atomic.StoreInt64(&MaxConn, 20000)

复制代码

The above code can be used at any time without locking.

This way, okay.

But look at the following requirements.

Multiple configuration variables to keep in sync

What is keeping in sync, for example, I have two configuration variables:

var MaxUser = 10000 // 某业务最大用户数
var MinUser = 8000 // 某业务最小用户数
复制代码

we want to guaranteeMaxUser > MinUser

Can atomic operations be done?

Obviously not, atomic operations cannot guarantee a certain relationship between multiple variables.

If I set new

  • MaxUser = 20000
  • MinUser = 15000

If, I set MaxUser first and then Minuser, it's fine.

However, if I set MinUser first, and then MaxUser, it takes a little while,

MinUser = 15000 // already set to new

MaxUser = 10000 // not yet set to new

This is not what is required.

Someone may have said, I just set MaxUser first.

Okay, if you have 100 variables, you have to take it slow, who comes first. I'm afraid everyone will faint.

Solve multi-variable synchronous lock-free read

Here is a simple solution.

We put all variables into a struct:


type ConfigVar struct {
    MaxConn int64
    MinConn int64
    ..
    ..
    ..
}
复制代码

Well, this ConfigVar is a whole. In our needs, we must ensure that,

  • When each thread takes its own value, it is a whole, not separate.
  • Each write is a whole, not separate.

To do both of these, we need some extra variables.

We can have an ConfigVararray of lengths 2:

var configVarArr  = make([]*ConfigVar, 2)
复制代码

We know that as long as we ensure that no one else is writing when we read, we will succeed.

Looking at the above array, with two elements, we just need to ensure that the subscript we read is different from the subscript we write!

so:

var configVarArr  = make([]*ConfigVar, 2)
var nowReadIndex int64 = 0
复制代码

nowReadIndexThe meaning of this variable is that the subscript currently used for reading is 0.

So when writing, it is natural to use subscripts 1.

We only need to change this subscript after writing.

That is, if the subscript is 0, turn it into 1.

If the subscript is 1, make it 0.

This way, reads and writes never use the same memory at the same time.

This achieves lock-free read and write.

Guess you like

Origin juejin.im/post/7082752962464284679
Recommended