github.com/dubbogo/gost@v1.14.0/sync/task_pool.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 gxsync
    19  
    20  import (
    21  	"fmt"
    22  	"log"
    23  	"math/rand"
    24  	"os"
    25  	"runtime"
    26  	"runtime/debug"
    27  	"sync"
    28  	"sync/atomic"
    29  	"time"
    30  )
    31  
    32  import (
    33  	gxruntime "github.com/dubbogo/gost/runtime"
    34  )
    35  
    36  type task func()
    37  
    38  // GenericTaskPool represents an generic task pool.
    39  type GenericTaskPool interface {
    40  	// AddTask wait idle worker add task
    41  	AddTask(t task) bool
    42  	// AddTaskAlways add task to queues or do it immediately
    43  	AddTaskAlways(t task)
    44  	// AddTaskBalance add task to idle queue
    45  	AddTaskBalance(t task)
    46  	// Close use to close the task pool
    47  	Close()
    48  	// IsClosed use to check pool status.
    49  	IsClosed() bool
    50  }
    51  
    52  func goSafely(fn func()) {
    53  	gxruntime.GoSafely(nil, false, fn, nil)
    54  }
    55  
    56  /////////////////////////////////////////
    57  // Task Pool
    58  /////////////////////////////////////////
    59  // task pool: manage task ts
    60  type TaskPool struct {
    61  	TaskPoolOptions
    62  
    63  	idx    uint32 // round robin index
    64  	qArray []chan task
    65  	wg     sync.WaitGroup
    66  
    67  	once sync.Once
    68  	done chan struct{}
    69  }
    70  
    71  // NewTaskPool build a task pool
    72  func NewTaskPool(opts ...TaskPoolOption) GenericTaskPool {
    73  	var tOpts TaskPoolOptions
    74  	for _, opt := range opts {
    75  		opt(&tOpts)
    76  	}
    77  
    78  	tOpts.validate()
    79  
    80  	p := &TaskPool{
    81  		TaskPoolOptions: tOpts,
    82  		qArray:          make([]chan task, tOpts.tQNumber),
    83  		done:            make(chan struct{}),
    84  	}
    85  
    86  	for i := 0; i < p.tQNumber; i++ {
    87  		p.qArray[i] = make(chan task, p.tQLen)
    88  	}
    89  	p.start()
    90  
    91  	return p
    92  }
    93  
    94  // start task pool
    95  func (p *TaskPool) start() {
    96  	for i := 0; i < p.tQPoolSize; i++ {
    97  		p.wg.Add(1)
    98  		workerID := i
    99  		q := p.qArray[workerID%p.tQNumber]
   100  		p.safeRun(workerID, q)
   101  	}
   102  }
   103  
   104  func (p *TaskPool) safeRun(workerID int, q chan task) {
   105  	gxruntime.GoSafely(nil, false,
   106  		func() {
   107  			err := p.run(int(workerID), q)
   108  			if err != nil {
   109  				// log error to stderr
   110  				log.Printf("gost/TaskPool.run error: %s", err.Error())
   111  			}
   112  		},
   113  		nil,
   114  	)
   115  }
   116  
   117  // worker
   118  func (p *TaskPool) run(id int, q chan task) error {
   119  	defer p.wg.Done()
   120  
   121  	var (
   122  		ok bool
   123  		t  task
   124  	)
   125  
   126  	for {
   127  		select {
   128  		case <-p.done:
   129  			if 0 < len(q) {
   130  				return fmt.Errorf("task worker %d exit now while its task buffer length %d is greater than 0",
   131  					id, len(q))
   132  			}
   133  
   134  			return nil
   135  
   136  		case t, ok = <-q:
   137  			if ok {
   138  				func() {
   139  					defer func() {
   140  						if r := recover(); r != nil {
   141  							fmt.Fprintf(os.Stderr, "%s goroutine panic: %v\n%s\n",
   142  								time.Now(), r, string(debug.Stack()))
   143  						}
   144  					}()
   145  					t()
   146  				}()
   147  			}
   148  		}
   149  	}
   150  }
   151  
   152  // return false when the pool is stop
   153  func (p *TaskPool) AddTask(t task) (ok bool) {
   154  	idx := atomic.AddUint32(&p.idx, 1)
   155  	id := idx % uint32(p.tQNumber)
   156  
   157  	select {
   158  	case <-p.done:
   159  		return false
   160  	default:
   161  		p.qArray[id] <- t
   162  		return true
   163  	}
   164  }
   165  
   166  func (p *TaskPool) AddTaskAlways(t task) {
   167  	id := atomic.AddUint32(&p.idx, 1) % uint32(p.tQNumber)
   168  
   169  	select {
   170  	case p.qArray[id] <- t:
   171  		return
   172  	default:
   173  		goSafely(t)
   174  	}
   175  }
   176  
   177  // do it immediately when no idle queue
   178  func (p *TaskPool) AddTaskBalance(t task) {
   179  	length := len(p.qArray)
   180  
   181  	// try len/2 times to lookup idle queue
   182  	for i := 0; i < length/2; i++ {
   183  		select {
   184  		case p.qArray[rand.Intn(length)] <- t:
   185  			return
   186  		default:
   187  			continue
   188  		}
   189  	}
   190  
   191  	goSafely(t)
   192  }
   193  
   194  // stop all tasks
   195  func (p *TaskPool) stop() {
   196  	select {
   197  	case <-p.done:
   198  		return
   199  	default:
   200  		p.once.Do(func() {
   201  			close(p.done)
   202  		})
   203  	}
   204  }
   205  
   206  // check whether the session has been closed.
   207  func (p *TaskPool) IsClosed() bool {
   208  	select {
   209  	case <-p.done:
   210  		return true
   211  
   212  	default:
   213  		return false
   214  	}
   215  }
   216  
   217  func (p *TaskPool) Close() {
   218  	p.stop()
   219  	p.wg.Wait()
   220  	for i := range p.qArray {
   221  		close(p.qArray[i])
   222  	}
   223  }
   224  
   225  /////////////////////////////////////////
   226  // Task Pool Simple
   227  /////////////////////////////////////////
   228  type taskPoolSimple struct {
   229  	work chan task     // task channel
   230  	sem  chan struct{} // gr pool size
   231  
   232  	wg sync.WaitGroup
   233  
   234  	once sync.Once
   235  	done chan struct{}
   236  }
   237  
   238  // NewTaskPoolSimple build a simple task pool
   239  func NewTaskPoolSimple(size int) GenericTaskPool {
   240  	if size < 1 {
   241  		size = runtime.GOMAXPROCS(-1) * 100
   242  	}
   243  	return &taskPoolSimple{
   244  		work: make(chan task),
   245  		sem:  make(chan struct{}, size),
   246  		done: make(chan struct{}),
   247  	}
   248  }
   249  
   250  func (p *taskPoolSimple) AddTask(t task) bool {
   251  	select {
   252  	case <-p.done:
   253  		return false
   254  	default:
   255  	}
   256  
   257  	select {
   258  	case <-p.done:
   259  		return false
   260  	case p.work <- t:
   261  	case p.sem <- struct{}{}:
   262  		p.wg.Add(1)
   263  		go p.worker(t)
   264  	}
   265  	return true
   266  }
   267  
   268  func (p *taskPoolSimple) AddTaskAlways(t task) {
   269  	select {
   270  	case <-p.done:
   271  		return
   272  	default:
   273  	}
   274  
   275  	select {
   276  	case p.work <- t:
   277  		// exec @t in gr pool
   278  		return
   279  	default:
   280  	}
   281  	select {
   282  	case p.work <- t:
   283  		// exec @t in gr pool
   284  	case p.sem <- struct{}{}:
   285  		// add a gr to the gr pool
   286  		p.wg.Add(1)
   287  		go p.worker(t)
   288  	default:
   289  		// gen a gr temporarily
   290  		goSafely(t)
   291  	}
   292  }
   293  
   294  func (p *taskPoolSimple) worker(t task) {
   295  	defer func() {
   296  		if r := recover(); r != nil {
   297  			fmt.Fprintf(os.Stderr, "%s goroutine panic: %v\n%s\n",
   298  				time.Now(), r, string(debug.Stack()))
   299  		}
   300  		p.wg.Done()
   301  		<-p.sem
   302  	}()
   303  	t()
   304  	for t := range p.work {
   305  		t()
   306  	}
   307  }
   308  
   309  // stop all tasks
   310  func (p *taskPoolSimple) stop() {
   311  	select {
   312  	case <-p.done:
   313  		return
   314  	default:
   315  		p.once.Do(func() {
   316  			close(p.done)
   317  			close(p.work)
   318  		})
   319  	}
   320  }
   321  
   322  func (p *taskPoolSimple) Close() {
   323  	p.stop()
   324  	// wait until all tasks done
   325  	p.wg.Wait()
   326  }
   327  
   328  // check whether the session has been closed.
   329  func (p *taskPoolSimple) IsClosed() bool {
   330  	select {
   331  	case <-p.done:
   332  		return true
   333  	default:
   334  		return false
   335  	}
   336  }
   337  
   338  func (p *taskPoolSimple) AddTaskBalance(t task) { p.AddTaskAlways(t) }