线程安全
线程安全是指在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。
设计实验与验证
使用 go 关键词 创建100个goroutine 来对数据库进行更新字段,让一个字段的值自增1,观察最后结果。
现有数据库test ,里面有student表,尝试修改第一条数据的age属性
实验代码如下
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"sync"
)
type Student struct {
ID uint
Name string
Age uint8
}
// 设置 `Student` 的表名为 `student`
func (Student) TableName() string {
return "student"
}
func main() {
/* 1. 创建一个数据库连接对象db */
dsn := "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil{
panic(err)
}
// 协程控制
var waitgroup sync.WaitGroup
waitgroup.Add(100)
for i := 0; i < 100; i++ {
go func(n int) {
fmt.Println(n)
db.Model(&Student{}).Where("id = ?", 1).Update("age", gorm.Expr("age + 1"))
waitgroup.Done()
}(i)
}
waitgroup.Wait()
}
实验结果
可见 gorm 对于数据库的操作是线程安全的
线程不安全的例子
这里我们以pymysql 常见的错误使用为例。设数据表结构相同,测试逻辑相同,测试代码如下
# pymysql线程安全性考量
import pymysql
import _thread
import threading
# 打开数据库连接
db = pymysql.connect(host="localhost", user="root", password="123456", db="test")
# 使用 cursor() 方法创建一个游标对象 cursor
cursor = db.cursor()
threadLock = threading.Lock()
def increase(num):
# 使用 execute() 方法执行 SQL 查询
print(num)
# threadLock.acquire() ** 线程锁 **
cursor.execute("UPDATE `test`.`student` SET `age` = `age`+1 WHERE `id` = 1")
db.commit()
print(num, "done")
# threadLock.release()
# 创建100 个线程
for i in range(1, 101):
_thread.start_new_thread(increase, (i,))
# 使用input 让主程序处于停滞状态
input()
执行代码会出现如下错误
pymysql.err.InternalError: Packet sequence number wrong - got 97 expected 2
多个线程同时共享了一个连接,发生了错误,要使上述代码和gorm的代码有一样的效果,需要我们手动上锁,即把threadLock.acquire()
和 threadLock.release()
取消注释
def increase(num):
# 使用 execute() 方法执行 SQL 查询
print(num)
threadLock.acquire() # ** 线程锁 **
cursor.execute("UPDATE `test`.`student` SET `age` = `age`+1 WHERE `id` = 1")
db.commit()
print(num, "done")
threadLock.release()