The reason for the deadlock is because your code will call the Lock()
method of the same mutex twice, which is a blocking operation.
The explanation lies in Spec: Selectors:
The following rules apply to selectors:
- For a value
x
of type T
or *T
where T
is not a pointer or interface type, x.f
denotes the field or method at the shallowest depth in T
where there is such an f
. If there is not exactly one f
with shallowest depth, the selector expression is illegal.
What does this mean?
In B
, you embed both a sync.Mutex
and a value of A
, and A
also embeds a sync.Mutex
.
When you write B.Mutex
, that could refer to the directly embedded B.Mutex
field (the unqualified type name acts as the field name), and could also refer to B.A.Mutex
(because the A
field is embedded in B
), but according to the quoted rule above, it will denote the field / method at the shallowest depth which is B.Mutex
.
Similarly, b.Lock()
could refer to B.Mutex.Lock()
and could refer to B.A.Mutex.Lock()
. But again according to the quoted rule, it will denote the field / method at the shallowest depth, which is B.Mutex.Lock()
.
So this code:
b.Lock()
b.Mutex.Lock()
Will call the Lock()
method of the same Mutex
twice, which is the embedded B.Mutex
field of the B
struct. The 2nd call will block, as the mutex is already locked.
When you rename A.Mutex
to e.g. A.Mutexx
, and then you write:
b.Lock()
b.Mutexx.Lock()
The first b.Lock()
call refers to B.Mutex.Lock()
, and the second b.Mutexx.Lock()
call refers to B.A.Mutexx.Lock()
call, so they lock 2 different, distinct mutexes; they are independent, so the 2nd lock will not block (its mutex is not yet locked).