github.com/grumpyhome/grumpy@v0.3.1-0.20201208125205-7b775405bdf1/grumpy-runtime-src/runtime/threading.go (about) 1 // Copyright 2016 Google Inc. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package grumpy 16 17 import ( 18 "sync" 19 "sync/atomic" 20 "unsafe" 21 ) 22 23 const ( 24 argsCacheSize = 16 25 argsCacheArgc = 6 26 ) 27 28 type threadState struct { 29 reprState map[*Object]bool 30 excValue *BaseException 31 excTraceback *Traceback 32 // argsCache is a small, per-thread LIFO cache for arg lists. Entries 33 // have a fixed capacity so calls to functions with larger parameter 34 // lists will be allocated afresh each time. Args freed when the cache 35 // is full are dropped. If the cache is empty then a new args slice 36 // will be allocated. 37 argsCache []Args 38 39 // frameCache is a local cache of allocated frames almost ready for 40 // reuse. The cache is maintained through the Frame `back` pointer as a 41 // singly linked list. 42 frameCache *Frame 43 } 44 45 func newThreadState() *threadState { 46 return &threadState{argsCache: make([]Args, 0, argsCacheSize)} 47 } 48 49 // recursiveMutex implements a typical reentrant lock, similar to Python's 50 // RLock. Lock can be called multiple times for the same frame stack. 51 type recursiveMutex struct { 52 mutex sync.Mutex 53 threadState *threadState 54 count int 55 } 56 57 func (m *recursiveMutex) Lock(f *Frame) { 58 p := (*unsafe.Pointer)(unsafe.Pointer(&m.threadState)) 59 if (*threadState)(atomic.LoadPointer(p)) != f.threadState { 60 // m.threadState != f.threadState implies m is not held by this 61 // thread and therefore we won't deadlock acquiring the mutex. 62 m.mutex.Lock() 63 // m.threadState is now guaranteed to be empty (otherwise we 64 // couldn't have acquired m.mutex) so store our own thread ID. 65 atomic.StorePointer(p, unsafe.Pointer(f.threadState)) 66 m.count++ 67 } else { 68 m.count++ 69 } 70 } 71 72 func (m *recursiveMutex) Unlock(f *Frame) { 73 p := (*unsafe.Pointer)(unsafe.Pointer(&m.threadState)) 74 if (*threadState)(atomic.LoadPointer(p)) != f.threadState { 75 logFatal("recursiveMutex.Unlock: frame did not match that passed to Lock") 76 } 77 // Since we're unlocking, we must hold m.mutex, so this is safe. 78 if m.count <= 0 { 79 logFatal("recursiveMutex.Unlock: Unlock called too many times") 80 } 81 m.count-- 82 if m.count == 0 { 83 atomic.StorePointer(p, unsafe.Pointer(nil)) 84 m.mutex.Unlock() 85 } 86 } 87 88 // TryableMutex is a mutex-like object that also supports TryLock(). 89 type TryableMutex struct { 90 c chan bool 91 } 92 93 // NewTryableMutex returns a new TryableMutex. 94 func NewTryableMutex() *TryableMutex { 95 m := &TryableMutex{make(chan bool, 1)} 96 m.Unlock() 97 return m 98 } 99 100 // Lock blocks until the mutex is available and then acquires a lock. 101 func (m *TryableMutex) Lock() { 102 <-m.c 103 } 104 105 // TryLock returns true and acquires a lock if the mutex is available, otherwise 106 // it returns false. 107 func (m *TryableMutex) TryLock() bool { 108 select { 109 case <-m.c: 110 return true 111 default: 112 return false 113 } 114 } 115 116 // Unlock releases the mutex's lock. 117 func (m *TryableMutex) Unlock() { 118 m.c <- true 119 }