Prompts for GPT
User
gopool.go
package gopoolimport ( "sync" )
type Task func()
type GoPool struct { Workers []*Worker MaxWorkers int workerStack []int taskQueue chan Task mutex sync.Mutex cond *sync.Cond }
func NewGoPool(maxWorkers int) *GoPool { pool := &GoPool{ MaxWorkers: maxWorkers, Workers: make([]*Worker, maxWorkers), workerStack: make([]int, maxWorkers), taskQueue: make(chan Task, 1e6), } pool.cond = sync.NewCond(&pool.mutex) for i := 0; i < maxWorkers; i++ { worker := newWorker() pool.Workers[i] = worker pool.workerStack[i] = i worker.start(pool, i) } go pool.dispatch() return pool }
func (p *GoPool) AddTask(task Task) { p.taskQueue <- task }
func (p *GoPool) Release() { close(p.taskQueue) p.cond.L.Lock() for len(p.workerStack) != p.MaxWorkers { p.cond.Wait() } p.cond.L.Unlock() for _, worker := range p.Workers { close(worker.TaskQueue) } p.Workers = nil p.workerStack = nil }
func (p *GoPool) popWorker() int { p.mutex.Lock() workerIndex := p.workerStack[len(p.workerStack)-1] p.workerStack = p.workerStack[:len(p.workerStack)-1] p.mutex.Unlock() return workerIndex }
func (p *GoPool) pushWorker(workerIndex int) { p.mutex.Lock() p.workerStack = append(p.workerStack, workerIndex) p.mutex.Unlock() p.cond.Signal() }
func (p *GoPool) dispatch() { for task := range p.taskQueue { p.cond.L.Lock() for len(p.workerStack) == 0 { p.cond.Wait() } p.cond.L.Unlock() workerIndex := p.popWorker() p.Workers[workerIndex].TaskQueue <- task } }
worker.go
package gopooltype Worker struct { TaskQueue chan Task }
func newWorker() *Worker { return &Worker{ TaskQueue: make(chan Task, 1), } }
func (w *Worker) start(pool *GoPool, workerIndex int) { go func() { for task := range w.TaskQueue { if task != nil { task() } pool.pushWorker(workerIndex) } }() }
gopool_test.go
package gopoolimport ( "sync" "testing" "time" )
func TestGoPool(t *testing.T) { pool := NewGoPool(100) for i := 0; i < 1000; i++ { pool.AddTask(func() { time.Sleep(10 * time.Millisecond) }) } pool.Release() }
func BenchmarkGoPool(b *testing.B) { var wg sync.WaitGroup var taskNum = int(1e6) pool := NewGoPool(1e4)
b.ResetTimer()
for i := 0; i < b.N; i++ {
wg.Add(taskNum)
for num := 0; num < taskNum; num++ {
pool.AddTask(func() {
time.Sleep(10 * time.Millisecond)
wg.Done()
})
}
}
wg.Wait()
// pool.Release()
}
func BenchmarkGoroutines(b *testing.B) { var wg sync.WaitGroup var taskNum = int(1e6)
for i := 0; i < b.N; i++ {
wg.Add(taskNum)
for num := 0; num < taskNum; num++ {
go func() {
time.Sleep(10 * time.Millisecond)
wg.Done()
}()
}
}
}
我想尝试通过 Functional Options 模式给 GoPool 添加配置,Option 中我希望先支持配置 GoPool 中的锁是使用互斥锁还是自旋锁,所以 GoPool.mutex 可以改成 lock sync.Locker。我已经找到了一个自旋锁的实现,地址是 github.com/daniel-hutao/spinlock,用法示例:
package main
import (
"github.com/daniel-hutao/spinlock"
)
func main() {
var sl spinlock.SpinLock
sl.Lock()
// critical section here
sl.Unlock()
}
Please update the code as required, and remember to also update the test cases.
DevChat
First, we need to change the type of mutex
in the GoPool
struct from sync.Mutex
to sync.Locker
. This way, we can use any lock that implements the sync.Locker
interface, including sync.Mutex
and spinlock.SpinLock
.
Next, we can add a function of type Option
that takes a *GoPool
argument and modifies its properties. We can add a WithLock
function that takes a sync.Locker
argument and sets it as the lock for GoPool
.
Finally, we need to modify the NewGoPool
function to accept a series of Option
functions as arguments and apply these functions after creating the GoPool
.
Below is the modified gopool.go
file:
package gopool
import (
"sync"
"github.com/daniel-hutao/spinlock"
)
type Task func()
type GoPool struct {
Workers []*Worker
MaxWorkers int
workerStack []int
taskQueue chan Task
lock sync.Locker
cond *sync.Cond
}
type Option func(*GoPool)
func WithLock(lock sync.Locker) Option {
return func(p *GoPool) {
p.lock = lock
p.cond = sync.NewCond(p.lock)
}
}
func NewGoPool(maxWorkers int, opts ...Option) *GoPool {
pool := &GoPool{
MaxWorkers: maxWorkers,
Workers: make([]*Worker, maxWorkers),
workerStack: make([]int, maxWorkers),
taskQueue: make(chan Task, 1e6),
lock: new(sync.Mutex),
}
for _, opt := range opts {
opt(pool)
}
if pool.cond == nil {
pool.cond = sync.NewCond(pool.lock)
}
for i := 0; i < maxWorkers; i++ {
worker := newWorker()
pool.Workers[i] = worker
pool.workerStack[i] = i
worker.start(pool, i)
}
go pool.dispatch()
return pool
}
// ... rest of the code ...
In this implementation, the NewGoPool
function now accepts a series of Option
functions as arguments and applies these functions after creating the GoPool
. The WithLock
function takes a sync.Locker
argument and sets it as the lock for GoPool
.
Next, we need to update the test cases to use the new NewGoPool
function. Below is the modified gopool_test.go
file:
package gopool
import (
"sync"
"testing"
"time"
"github.com/daniel-hutao/spinlock"
)
func TestGoPool(t *testing.T) {
pool := NewGoPool(100, WithLock(new(spinlock.SpinLock)))
for i := 0; i < 1000; i++ {
pool.AddTask(func() {
time.Sleep(10 * time.Millisecond)
})
}
pool.Release()
}
// ... rest of the code ...
In this implementation, we use the WithLock
function to set the lock for GoPool
as spinlock.SpinLock
.
User
gopool_test.go
package gopoolimport ( "sync" "testing" "time"
"github.com/daniel-hutao/spinlock"
)
func TestGoPool(t *testing.T) { pool := NewGoPool(100, WithLock(new(spinlock.SpinLock))) for i := 0; i < 1000; i++ { pool.AddTask(func() { time.Sleep(10 * time.Millisecond) }) } pool.Release() }
func BenchmarkGoPool(b *testing.B) { var wg sync.WaitGroup var taskNum = int(1e6) pool := NewGoPool(1e4)
b.ResetTimer()
for i := 0; i < b.N; i++ {
wg.Add(taskNum)
for num := 0; num < taskNum; num++ {
pool.AddTask(func() {
time.Sleep(10 * time.Millisecond)
wg.Done()
})
}
}
wg.Wait()
pool.Release()
}
func BenchmarkGoroutines(b *testing.B) { var wg sync.WaitGroup var taskNum = int(1e6)
for i := 0; i < b.N; i++ {
wg.Add(taskNum)
for num := 0; num < taskNum; num++ {
go func() {
time.Sleep(10 * time.Millisecond)
wg.Done()
}()
}
}
}
Please add test cases using mutex, which means the current 3 cases should become 5.
DevChat
As per your request, I will add two new test cases for GoPool
, one using sync.Mutex
and the other using spinlock.SpinLock
. Below is the modified gopool_test.go
file:
package gopool
import (
"sync"
"testing"
"time"
"github.com/daniel-hutao/spinlock"
)
func TestGoPoolWithMutex(t *testing.T) {
pool := NewGoPool(100, WithLock(new(sync.Mutex)))
for i := 0; i < 1000; i++ {
pool.AddTask(func() {
time.Sleep(10 * time.Millisecond)
})
}
pool.Release()
}
func TestGoPoolWithSpinLock(t *testing.T) {
pool := NewGoPool(100, WithLock(new(spinlock.SpinLock)))
for i := 0; i < 1000; i++ {
pool.AddTask(func() {
time.Sleep(10 * time.Millisecond)
})
}
pool.Release()
}
func BenchmarkGoPoolWithMutex(b *testing.B) {
var wg sync.WaitGroup
var taskNum = int(1e6)
pool := NewGoPool(1e4, WithLock(new(sync.Mutex)))
b.ResetTimer()
for i := 0; i < b.N; i++ {
wg.Add(taskNum)
for num := 0; num < taskNum; num++ {
pool.AddTask(func() {
time.Sleep(10 * time.Millisecond)
wg.Done()
})
}
}
wg.Wait()
pool.Release()
}
func BenchmarkGoPoolWithSpinLock(b *testing.B) {
var wg sync.WaitGroup
var taskNum = int(1e6)
pool := NewGoPool(1e4, WithLock(new(spinlock.SpinLock)))
b.ResetTimer()
for i := 0; i < b.N; i++ {
wg.Add(taskNum)
for num := 0; num < taskNum; num++ {
pool.AddTask(func() {
time.Sleep(10 * time.Millisecond)
wg.Done()
})
}
}
wg.Wait()
pool.Release()
}
func BenchmarkGoroutines(b *testing.B) {
var wg sync.WaitGroup
var taskNum = int(1e6)
for i := 0; i < b.N; i++ {
wg.Add(taskNum)
for num := 0; num < taskNum; num++ {
go func() {
time.Sleep(10 * time.Millisecond)
wg.Done()
}()
}
}
}
In this implementation, I added two new test cases TestGoPoolWithMutex
and TestGoPoolWithSpinLock
, which test GoPool
using sync.Mutex
and spinlock.SpinLock
respectively. I also added two new benchmark test cases BenchmarkGoPoolWithMutex
and BenchmarkGoPoolWithSpinLock
, which benchmark GoPool
using sync.Mutex
and spinlock.SpinLock
respectively.