[GO]Some notes on Receivers 作者: rin 时间: August 17, 2023 分类: Go 评论 # Review of receivers When a value receiver is defined, Go automatically generates a corresponding pointer receiver. Auto-generating a pointer receiver causes differences in the method sets of pointer types and value types. For example, take the `Len()` method below, Go will automatically generate a corresponding pointer receiver version: ```go type List []int func (l List) Len() int { return len(l) } // func (l *List) Len() int { ... } func (l *List) Append(val int) { *l = append(*l, val) } ``` For type `List`, its method set includes: - `Len() int` For type `*List`, the method set includes: - `Len() int` - `Append(int)` This difference affects the use of interfaces: ```go type Container interface { Len() int Append(int) } var _ Container = List{} // no var _ Container = &List{} ``` However, for the type's own pointer and value, there appears to be no difference, both can call the two methods: ```go l := List{} pl := &List{} // List only implements `Len` l.Append(42) // how does Go find `Append`? // (&l).Append(42) l.Len() // gets 1 // *List implements `Append` explicitly, `Len` implicitly pl.Append(6) pl.Len() // gets 1 ``` In the `List` method set, there is no `Append(int)`. Go converts the corresponding call to `(&l).Append(42)` to implement this which is a syntactic sugar. However, if you cannot get the address of an object of a certain type, this feature does not work. Non-addressable types include `map` and `interface` and some other, as shown below: ```go m := map[string]List{"list1": List{}} p := &m["list1"] // cannot take the address of m["list1"] (map index expression of type List) var i interface{} = List{} p = &i.(List) // cannot take the address of i.(List) ``` Because the map stores `List`, you can directly call `List.Len()`. Since you cannot take the address of a map element, you cannot convert it to call `*List.Append()`. The same applies to `interface`: ```go fmt.Println(m["list1"].Len()) // ok m["list1"].Append(42) // bad, cannot call the pointer method Append on List i.(List).Len() // ok i.(List).Append() // bad, cannot call the pointer method Append on List ``` # What Do Auto-Generated Pointer Receivers Do If the auto-generated pointer receiver's function body is the same as the value receiver, can I modify the object's value through the pointer? No. In fact, the pointer receiver calls the value receiver and passes arguments by value. When using `dlv debug main.go` to debug `main.(*List).Len`, I found that it doesn't stop at this function breakpoint. Reviewing the previous code, `pl.Len()` may have been converted to `(*pl).Len()`, which caused it to use the value receiver. Therefore, it is necessary to forcefully use the pointer receiver: ```go l := List{} pl := &List{} // List only implements `Len` l.Append(42) // how does Go find `Append`? // (&l).Append(42) l.Len() // gets 1 // *List implements `Append` explicitly, `Len` implicitly pl.Append(6) pl.Len() // gets 1 // Forcefully call `func(l *List) Len()` (*List).Len(pl) // gets 1 ``` It is confirmed that `main.(*List).Len` internally calls `main.List.Len`. ![](http://static.lo-li.net/images/2023/10/2023-10-20_19-44.png) ![](http://static.lo-li.net/images/2023/10/2023-10-20_19-45.png)
[CMU15445] 锁与事务隔离级别 作者: rin 时间: August 16, 2023 分类: 未分类 评论 「基于锁的并发控制」与「多版本并发控制」是实现事务隔离级别的两种不同思路。在两阶段锁中,事务分为获取锁的**增长阶段**和释放锁的**收缩阶段**。在不同的阶段中通过设置不同的规则和限制可以实现不同的隔离级别。例如: 1. 读取数据时是否需要锁?需要什么类型的锁? 2. 什么时候释放锁? 不同的隔离级别下,事务修改数据时都需要X锁,并在事务结束时释放。这将保证两个写事务之间不会产生**脏写**的问题,这种问题在数据库中是不允许的。 因此在不同隔离级别下都可能会用到X锁,但S锁不一定。 --- **READ UNCOMMITTED** 1. 事务读数据时不需要S锁,只需要在写数据时获取X锁。这样当前事务的读操作就不会被其他事务的X锁阻塞; 2. 事务结束时释放X锁。释放X锁事务则进入收缩阶段。 由于读操作不会被阻塞,可以随时读到别的事务修改的数据,因此存在**脏读**问题。 --- **READ COMMITTED** 1. 当前事务读数据时需要S锁,以阻塞之后其他事务对数据的修改。同样,当其他事务先获取了X锁并修改数据时,当前事务对S锁的获取将被阻塞,直到其他事务的X锁释放才能读到数据。 2. 事务结束时释放X锁,释放X锁事务则进入收缩阶段。释放S锁不改变事务的阶段,因此可以随时释放(?) READ COMMITTED解决了脏读问题:S锁会被X锁阻塞+事务结束时才释放X锁=只能读到提交/回滚的数据。 但是没有解决不可重复读的问题:当前事务读完数据之后可以释放S锁,这时其他事务可以修改相应的数据并提交,当前事务再次读取时将得到不同的数据,即**不可重复读**问题。 --- **REPEATABLE READ** 1. 与READ COMMITTED相同 2. 事务结束时释放X锁与S锁。事务进入收缩阶段。 由于S锁也会被保留到事务结束,因此其他事务无法修改相应的数据,当前事务中多次读取得到的也是同样的数据。 | | S锁 | X锁 | | --- | ------ | --- | | RU | N/A | 结束时释放 | | RC | 读后释放 | 结束时释放 | | RR | 结束时释放 | 结束时释放 |