github.com/dubbogo/gost@v1.14.0/container/queue/queue.go (about) 1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package gxqueue 19 20 import ( 21 "errors" 22 "runtime" 23 "sync" 24 "sync/atomic" 25 "time" 26 ) 27 28 var ( 29 // ErrDisposed is returned when an operation is performed on a disposed 30 // queue. 31 ErrDisposed = errors.New(`queue: disposed`) 32 33 // ErrTimeout is returned when an applicable queue operation times out. 34 ErrTimeout = errors.New(`queue: poll timed out`) 35 36 // ErrEmptyQueue is returned when an non-applicable queue operation was called 37 // due to the queue's empty item state 38 ErrEmptyQueue = errors.New(`queue: empty queue`) 39 ) 40 41 // waiters is the struct responsible for store sema(waiter better) of queue. 42 type waiters []*sema 43 44 func (w *waiters) get() *sema { 45 if len(*w) == 0 { 46 return nil 47 } 48 49 sema := (*w)[0] 50 copy((*w)[0:], (*w)[1:]) 51 (*w)[len(*w)-1] = nil // or the zero value of T 52 *w = (*w)[:len(*w)-1] 53 return sema 54 } 55 56 func (w *waiters) put(sema *sema) { 57 *w = append(*w, sema) 58 } 59 60 func (w *waiters) remove(sema *sema) { 61 if len(*w) == 0 { 62 return 63 } 64 for i := range *w { 65 if (*w)[i] == sema { 66 *w = append((*w)[:i], (*w)[i+1:]...) 67 return 68 } 69 } 70 } 71 72 // items is the struct responsible for store queue data 73 type items []interface{} 74 75 func (items *items) get(number int64) []interface{} { 76 index := int(number) 77 if int(number) > len(*items) { 78 index = len(*items) 79 } 80 81 returnItems := make([]interface{}, 0, index) 82 returnItems = returnItems[:index] 83 84 copy(returnItems[:index], (*items)) 85 86 *items = (*items)[index:] 87 return returnItems 88 } 89 90 func (items *items) peek() (interface{}, bool) { 91 if len(*items) == 0 { 92 return nil, false 93 } 94 95 return (*items)[0], true 96 } 97 98 func (items *items) getUntil(checker func(item interface{}) bool) []interface{} { 99 length := len(*items) 100 101 if len(*items) == 0 { 102 // returning nil here actually wraps that nil in a list 103 // of interfaces... thanks go 104 return []interface{}{} 105 } 106 107 returnItems := make([]interface{}, 0, length) 108 index := -1 109 for i, item := range *items { 110 if !checker(item) { 111 break 112 } 113 114 returnItems = append(returnItems, item) 115 index = i 116 (*items)[i] = nil // prevent memory leak 117 } 118 119 *items = (*items)[index+1:] 120 return returnItems 121 } 122 123 // sema is the struct responsible for tracking the state 124 // of waiter. blocking poll if no data, notify if new data comes in. 125 type sema struct { 126 ready chan bool 127 response *sync.WaitGroup 128 } 129 130 func newSema() *sema { 131 return &sema{ 132 ready: make(chan bool, 1), 133 response: &sync.WaitGroup{}, 134 } 135 } 136 137 // Queue is the struct responsible for tracking the state 138 // of the queue. 139 type Queue struct { 140 waiters waiters 141 items items 142 lock sync.Mutex 143 disposed int32 144 } 145 146 // New is a constructor for a new threadsafe queue. 147 func New(hint int64) *Queue { 148 return &Queue{ 149 items: make([]interface{}, 0, hint), 150 } 151 } 152 153 // Put will add the specified items to the queue. 154 func (q *Queue) Put(items ...interface{}) error { 155 if len(items) == 0 { 156 return nil 157 } 158 159 q.lock.Lock() 160 defer q.lock.Unlock() 161 162 if atomic.LoadInt32(&q.disposed) == 1 { 163 return ErrDisposed 164 } 165 166 q.items = append(q.items, items...) 167 for { 168 sema := q.waiters.get() 169 if sema == nil { 170 break 171 } 172 sema.response.Add(1) 173 select { 174 case sema.ready <- true: 175 sema.response.Wait() 176 default: 177 // This semaphore timed out. 178 } 179 if len(q.items) == 0 { 180 break 181 } 182 } 183 184 return nil 185 } 186 187 // Get retrieves items from the queue. If there are some items in the 188 // queue, get will return a number UP TO the number passed in as a 189 // parameter. If no items are in the queue, this method will pause 190 // until items are added to the queue. 191 func (q *Queue) Get(number int64) ([]interface{}, error) { 192 return q.Poll(number, 0) 193 } 194 195 // Poll retrieves items from the queue. If there are some items in the queue, 196 // Poll will return a number UP TO the number passed in as a parameter. If no 197 // items are in the queue, this method will pause until items are added to the 198 // queue or the provided timeout is reached. A non-positive timeout will block 199 // until items are added. If a timeout occurs, ErrTimeout is returned. 200 func (q *Queue) Poll(number int64, timeout time.Duration) ([]interface{}, error) { 201 if number < 1 { 202 // thanks again go 203 return []interface{}{}, nil 204 } 205 206 q.lock.Lock() 207 208 if atomic.LoadInt32(&q.disposed) == 1 { 209 q.lock.Unlock() 210 return nil, ErrDisposed 211 } 212 213 var items []interface{} 214 215 if len(q.items) == 0 { 216 sema := newSema() 217 q.waiters.put(sema) 218 q.lock.Unlock() 219 220 var timeoutC <-chan time.Time 221 if timeout > 0 { 222 timer := time.NewTimer(timeout) 223 defer timer.Stop() 224 225 timeoutC = timer.C 226 } 227 select { 228 case <-sema.ready: 229 // we are now inside the put's lock 230 if atomic.LoadInt32(&q.disposed) == 1 { 231 return nil, ErrDisposed 232 } 233 items = q.items.get(number) 234 sema.response.Done() 235 return items, nil 236 case <-timeoutC: 237 // cleanup the sema that was added to waiters 238 select { 239 case sema.ready <- true: 240 // we called this before Put() could 241 // Remove sema from waiters. 242 q.lock.Lock() 243 q.waiters.remove(sema) 244 q.lock.Unlock() 245 default: 246 // Put() got it already, we need to call Done() so Put() can move on 247 sema.response.Done() 248 } 249 return nil, ErrTimeout 250 } 251 } 252 253 items = q.items.get(number) 254 q.lock.Unlock() 255 return items, nil 256 } 257 258 // Peek returns a the first item in the queue by value 259 // without modifying the queue. 260 func (q *Queue) Peek() (interface{}, error) { 261 q.lock.Lock() 262 defer q.lock.Unlock() 263 264 if atomic.LoadInt32(&q.disposed) == 1 { 265 return nil, ErrDisposed 266 } 267 268 peekItem, ok := q.items.peek() 269 if !ok { 270 return nil, ErrEmptyQueue 271 } 272 273 return peekItem, nil 274 } 275 276 // GetUntil gets a function and returns a list of items that 277 // match the checker until the checker returns false. This does not 278 // wait if there are no items in the queue. 279 func (q *Queue) GetUntil(checker func(item interface{}) bool) ([]interface{}, error) { 280 if checker == nil { 281 return nil, nil 282 } 283 284 q.lock.Lock() 285 286 if atomic.LoadInt32(&q.disposed) == 1 { 287 q.lock.Unlock() 288 return nil, ErrDisposed 289 } 290 291 result := q.items.getUntil(checker) 292 q.lock.Unlock() 293 return result, nil 294 } 295 296 // Empty returns a bool indicating if this bool is empty. 297 func (q *Queue) Empty() bool { 298 q.lock.Lock() 299 defer q.lock.Unlock() 300 301 return len(q.items) == 0 302 } 303 304 // Len returns the number of items in this queue. 305 func (q *Queue) Len() int64 { 306 q.lock.Lock() 307 defer q.lock.Unlock() 308 309 return int64(len(q.items)) 310 } 311 312 // Disposed returns a bool indicating if this queue 313 // has had disposed called on it. 314 func (q *Queue) Disposed() bool { 315 q.lock.Lock() 316 defer q.lock.Unlock() 317 318 return atomic.LoadInt32(&q.disposed) == 1 319 } 320 321 // Dispose will dispose of this queue and returns 322 // the items disposed. Any subsequent calls to Get 323 // or Put will return an error. 324 func (q *Queue) Dispose() []interface{} { 325 q.lock.Lock() 326 defer q.lock.Unlock() 327 328 atomic.StoreInt32(&q.disposed, 1) 329 for _, waiter := range q.waiters { 330 waiter.response.Add(1) 331 select { 332 case waiter.ready <- true: 333 // release Poll immediately 334 default: 335 // ignore if it's a timeout or in the get 336 } 337 } 338 339 disposedItems := q.items 340 341 q.items = nil 342 q.waiters = nil 343 344 return disposedItems 345 } 346 347 // ExecuteInParallel will (in parallel) call the provided function 348 // with each item in the queue until the queue is exhausted. When the queue 349 // is exhausted execution is complete and all goroutines will be killed. 350 // This means that the queue will be disposed so cannot be used again. 351 func ExecuteInParallel(q *Queue, fn func(interface{})) { 352 if q == nil { 353 return 354 } 355 356 q.lock.Lock() // so no one touches anything in the middle 357 // of this process 358 length, count := uint64(len(q.items)), int64(-1) 359 // this is important or we might face an infinite loop 360 if length == 0 { 361 return 362 } 363 364 numCPU := 1 365 if runtime.NumCPU() > 1 { 366 numCPU = runtime.NumCPU() - 1 367 } 368 369 var wg sync.WaitGroup 370 wg.Add(numCPU) 371 items := q.items 372 373 for i := 0; i < numCPU; i++ { 374 go func() { 375 for { 376 index := atomic.AddInt64(&count, 1) 377 if index >= int64(length) { 378 wg.Done() 379 break 380 } 381 382 fn(items[index]) 383 items[index] = 0 384 } 385 }() 386 } 387 wg.Wait() 388 q.lock.Unlock() 389 q.Dispose() 390 }