转载自公众号:码农翻身 作者刘欣 前IBM架构师
旺财和小强生活在一个网上商城的系统中, 是一对儿线程好基友。
星期一刚上班,旺财接到领导电话说,要把一个商品的库存减少20, 旺财不敢怠慢,赶快把库存取出来一看,哦,现在有1000个。
与此同时,小强也接到电话说要把同一商品的库存减少30, 他一看,哦,现在有1000个。
旺财计算出最新的库存值980, 保存!
小强也计算出最新的库存值970, 保存 !
旺财的数据被小强覆盖了!
领导一看,本来卖出了50个商品,现在库存只扣了30个,这样持续下去就天下大乱了。
旺财和小强, 各打二十大板, 长长记性!
小强说:“哥,要不我们还是想个办法吧,再这样下去要被打死的。”
旺财悲催地说: “这样, 以后我们每次访问库存之前,都要先加锁,加了锁,就禁止别人再进入访问,只能等待持有锁的人来释放。”
星期二, 领导让旺财再次把库存减少20 , 旺财这次万分小心,先把库存给锁住,然后慢慢修改。
小强也接到了把库存减少的指令, 但是旺财哥已经把库存锁住了, 不能操作,小强只好去阻塞车间喝茶聊天,然后到就绪车间等待调度运行。
好不容易等到可以再次执行了,小强一看,这库存怎么还锁着呢!? 只好再次去阻塞车间喝茶。
领导一看, 小强你怎么回事, 老是喝茶聊天? ! 还干不干活了?
小强争辩说旺财哥一直锁着库存,我没法操作。
领导不管这些, 把小强和旺财又打了二十大板。
(备注: 这种加锁的方式就是悲观锁了,悲观锁正如其名,每次取读写数据时候总认为数据会被别人修改,所以将数据加锁,置于锁定状态, 不让别人再访问。缺点是如果持有锁的时间太长,其他用户需要等待很长时间。)
旺财说: “兄弟,这一次哥对不住你啊,处理得慢了一些, 不过哥刚才挨打的时候想了一个好办法:乐观锁。”
小强说:“拉倒吧你,屁股都快被打烂了还乐观?”
“你听我说嘛, 我们在那个库存字段的旁边,再加上一个版本(version)的字段, 例如刚开始的时候(库存= 1000, 版本=1), 每次你去读的时候不仅要读出库存,还要读出版本号, 等到你修改了库存,往回写的时候一定要检查一下版本号,看看和你读的时候是否一样。”
“如果不一样呢?” 小强问
“那就放弃这次写的操作,重新读取库存和版本号, 重新来过。”
“如果一样呢? ”
“那就放心大胆地把新的库存值写回去。把版本号也加1”
“我似乎有点明白了,我们试试,不过你要想好,我可不想再挨板子了。”
星期三, 旺财奉命把库存减去30, 他先读到了(库存= 1000, 版本=1); 小强也要改库存了,他要把库存减去50, 于是他也读到了(库存= 1000, 版本=1)。
旺财计算出新的库存值970 ,写回成功,现在版本变成了(库存= 970, 版本=2)。
小强也计算出新库存950 , 也准备写回,他战战兢兢地去看最新的版本号, 已经变成版本2了, 按照之前的约定,只好重新读取了。
小强再次读取(库存= 970, 版本=2) , 计算出最新缓存值920(970减去50), 再次试图更新,没想到又被别人抢了先,现在版本号已经变成3了 ,最新的数据是(库存= 900, 版本= 3)。
唉, 只好重新来过, 计算出最新缓存值850(900减去50),第三次终于更新成功了, 最新的库存是 (库存=850, 版本= 4)
领导看到旺财和小强忙得热火朝天,一刻不停,并且库存值也没有乱掉, 满意地点了点头:好同志啊。
(备注: 这种方式就是所谓的乐观锁了,旺财和小强这次乐观了一点,觉得一般情况下不会有太多人修改库存,所以没有加锁,放心地去操作,只有在最后更新的时候才去看是否冲突。 这种方式适合于冲突不多的场景,如果冲突很多,数据争用激烈,会导致不断地尝试,反而降低了性能。)
—————END—————
package main
import "fmt"
func main() {
fmt.Println("main start")
a()
fmt.Println("main end")
}
func a() {
fmt.Println("a start")
defer func() {
fmt.Println("c")
if err := recover(); err != nil {
fmt.Println(err) //这里的err其实就是panic传入的内容,55
}
fmt.Println("d")
}()
f()
fmt.Println("a end")
}
func f() {
fmt.Println("f start")
panic(55)
fmt.Println("f end")
}
输出:
main start
a start
f start
c
55
d
main end
package main
import (
"fmt"
)
func main() {
A()
B()
C()
}
func A() {
fmt.Println("Func A")
}
func B() {
defer func() {
if err := recover(); err != nil {
fmt.Println("Recover in B")
}
}()
panic("Panic in B")
}
func C() {
fmt.Println("Func C")
}
输出:
Func A
Recover in B
Func C
一、在函数体执行结束后按照调用顺序的相反顺序逐个执行。
二、即使函数发生严重错误也会执行。
三、支持匿名函数的调用。
四、通常用于资源清理、文件关闭、解锁以及记录时间等操作。
五、通过与匿名函数配合可在 return 之后修改函数的计算结果。
六、如果函数体内某个变量作为 defer 时匿名函数的参数,则在定义 defer 时即已经获得了拷贝,否则则是引用某个变量的地址。
七、panic 可以在任何地方引发,但 recover 只有在 defer 调用的函数中有效。
八、个人测试,ctrl-C 和 killall 的时候,defer 没有被执行。
package main
import "fmt"
func main() {
fmt.Println("f1() = ", f1())
fmt.Println("f2() = ", f2())
//输出
//f1() = 1
//f2() = 0
}
func f1() (result int) {
defer func() {
result++
}()
return 0
}
func f2() (result int) {
return 0
defer func() {
result++
}()
return 0
}
package main
import (
"log"
)
func main() {
defer log.Println("11111111")
defer log.Println("22222222")
defer log.Println("33333333")
}
/*
输出:
2018/02/27 09:01:21 33333333
2018/02/27 09:01:21 22222222
2018/02/27 09:01:21 11111111
*/
package main
import (
"log"
)
func main() {
defer log.Println("11111111")
defer log.Println("22222222")
defer log.Println("33333333")
panic("I panic in here.")
}
/*
输出:
2018/02/27 09:01:21 33333333
2018/02/27 09:01:21 22222222
2018/02/27 09:01:21 11111111
panic: I panic in here.
goroutine 1 [running]:
main.main()
e:/Go/GOPATH/src/jian/test/test.go:11 +0x172
exit status 2
*/
package main
import (
"log"
)
func main() {
defer log.Println("11111111")
f1()
defer log.Println("55555555")
}
func f1() {
defer log.Println("22222222")
defer log.Println("33333333")
panic("I panic in here.")
defer log.Println("44444444") //unreachable code.
}
/*
输出:
$ go run test.go
2018/02/27 09:09:46 33333333
2018/02/27 09:09:46 22222222
2018/02/27 09:09:46 11111111
panic: I panic in here.
goroutine 1 [running]:
main.f1()
e:/Go/GOPATH/src/jian/test/test.go:16 +0x10c
main.main()
e:/Go/GOPATH/src/jian/test/test.go:9 +0x93
exit status 2
*/
package main
import (
"fmt"
)
func main() {
for i := 0; i < 3; i++ {
defer func(i int) {
fmt.Println(i)
}(i)
}
}
/*
输出:
2
1
0
*/
package main
import (
"fmt"
)
func main() {
for i := 0; i < 3; i++ {
defer func() {
fmt.Println(i)
}()
}
}
/*
输出:
3
3
3
*/
因为 i 没有作为参数传递进去,所以每个闭包函数都是引用同一个 i 变量。
package main
func main() {
test()
}
func test() {
println("test start")
defer println("test end")
for i := 0; i < 5; i++ {
println("i =", i)
defer println("defer i =", i)
}
println("end of loop")
}
/*
输出:
test start
i = 0
i = 1
i = 2
i = 3
i = 4
end of loop
defer i = 4
defer i = 3
defer i = 2
defer i = 1
defer i = 0
test end
*/
这个输出说明了在循环体中的 defer 一样要等到函数结束了才会被执行,如果这个 defer 后面是关闭文件的话,那就会有大量的文件描述符被占用,这个时候就不能使用 defer。
看下是否完全弄懂 defer
package main
import "fmt"
func main() {
var fs = [4]func(){}
for i := 0; i < 4; i++ {
defer fmt.Println("defer i = ", i)
defer func() {
fmt.Println("defer_closure i = ", i)
}()
fs[i] = func() {
fmt.Println("closure i = ", i)
}
}
for _, f := range fs {
f()
}
}
输出:
closure i = 4
closure i = 4
closure i = 4
closure i = 4
defer_closure i = 4
defer i = 3
defer_closure i = 4
defer i = 2
defer_closure i = 4
defer i = 1
defer_closure i = 4
defer i = 0
redis-server.exe redis.windows.conf
默认为127.0.0.1:6379
redis-cli -h host -p port -a password
redis-cli.exe -h 127.0.0.1 -p 6379
可简写为redis-cli
config get * : 获取所有的信息
config get requirepass : 获取密码
config get dir : 获取redis的目录
config get requirepass
设置密码: config set requirepass “123456”
auth “123456”
取消密码: config set requirepass “”
save : 该命名将在redis目录下创建dump.rdb文件。
bgsave: 该命令将在后台执行。
如果需要恢复数据,只需将备份文件 (dump.rdb) 移动到 redis 安装目录并重新启动服务即可。
添加,查看
127.0.0.1:6379> hset runoobkey name "huangjian"
(integer) 0
127.0.0.1:6379> hget runoobkey name
"huangjian"
同时添加多个
127.0.0.1:6379> hmset runoobkey name "jian" age "123"
OK
127.0.0.1:6379> hgetall runoobkey
1) "name"
2) "jian"
3) "age"
4) "123"
127.0.0.1:6379> hget runoobkey name
"jian"
127.0.0.1:6379> hget runoobkey age
"123"
删除
127.0.0.1:6379> hdel runoobkey age
(integer) 1
127.0.0.1:6379> hgetall runoobkey
1) "name"
2) "huangjian"
127.0.0.1:6379> hdel runoobkey age
(integer) 0
判断键是否存在
127.0.0.1:6379> hexists runoobkey age
(integer) 0
127.0.0.1:6379> hexists runoobkey name
(integer) 1
在客户端1订阅: subscribe mychannel
在客户端2发布消息: publish mychannel “Redis is great”
然后客户端1就可以收到。
redis-cli keys ‘*’
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"os"
)
func main() {
genRsaKey(1024)
}
func genRsaKey(bits int) error {
privateKey, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
return err
}
derStream := x509.MarshalPKCS1PrivateKey(privateKey)
block := &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: derStream,
}
file, err := os.Create("private.pem")
if err != nil {
return err
}
err = pem.Encode(file, block)
if err != nil {
return err
}
publicKey := &privateKey.PublicKey
derPkix, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
return err
}
block = &pem.Block{
Type: "PUBLIC KEY",
Bytes: derPkix,
}
file, err = os.Create("public.pem")
if err != nil {
return err
}
err = pem.Encode(file, block)
if err != nil {
return err
}
return nil
}
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"io/ioutil"
)
var (
publicKey []byte
privateKey []byte
)
func init() {
pub, _ := ioutil.ReadFile("../genRSAKey/public.pem")
publicKey = []byte(pub)
pri, _ := ioutil.ReadFile("../genRSAKey/private.pem")
privateKey = []byte(pri)
}
func RsaEncrypt(origData []byte) ([]byte, error) {
block, _ := pem.Decode(publicKey)
if block == nil {
return nil, errors.New("public key error")
}
pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, err
}
pub := pubInterface.(*rsa.PublicKey)
return rsa.EncryptPKCS1v15(rand.Reader, pub, origData)
}
func RsaDecrypt(ciphertext []byte) ([]byte, error) {
block, _ := pem.Decode(privateKey)
if block == nil {
return nil, errors.New("Private key error!")
}
priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
return rsa.DecryptPKCS1v15(rand.Reader, priv, ciphertext)
}
func main() {
data, err := RsaEncrypt([]byte("1342042894@qq.com"))
if err != nil {
panic(err)
}
fmt.Println(string(data))
origData, err := RsaDecrypt(data)
if err != nil {
panic(err)
}
fmt.Println(string(origData))
}
package main
import (
"fmt"
"time"
"github.com/garyburd/redigo/redis"
)
func newPool(addr string) *redis.Pool {
return &redis.Pool{
MaxIdle: 3,
IdleTimeout: 240 * time.Second,
Dial: func() (redis.Conn, error) { return redis.Dial("tcp", addr) },
}
}
func main() {
testBasic()
testHash()
testPubSub()
}
func testPubSub() {
pool := newPool("127.0.0.1:6379")
psc := redis.PubSubConn{Conn: pool.Get()}
psc.Subscribe("mychannel")
for {
switch v := psc.Receive().(type) {
case redis.Message:
fmt.Println("redis.Message", v.Channel, v.Data)
case redis.Subscription:
fmt.Println("redis.Subscription")
case redis.PMessage:
fmt.Println("redis.PMessage")
case error:
fmt.Println("find an error")
}
}
}
func testHash() {
pool := newPool("127.0.0.1:6379")
conn := pool.Get()
defer conn.Close()
testHGET(conn)
testHSET(conn)
testHGET(conn)
testHDEL(conn)
testHGET(conn)
}
func testHSET(conn redis.Conn) {
conn.Do("HSET", "user1024", "roomID", 123)
}
func testHGET(conn redis.Conn) {
buf, err := redis.String(conn.Do("HGET", "user1024", "roomID"))
if err != nil && err != redis.ErrNil {
fmt.Println("Get user room err:", err)
return
}
fmt.Println("buf:", buf)
}
func testHDEL(conn redis.Conn) {
conn.Do("HDEL", "user1024", "roomID")
}
func testBasic() {
pool := newPool("127.0.0.1:6379")
conn := pool.Get()
defer conn.Close()
conn.Do("SET", "hello", "world")
s, _ := redis.String(conn.Do("GET", "hello"))
fmt.Printf("%#v\n", s)
}