go并发编程系列六:线程分组及控制线程的合作执行
背景:线程的合作执行,体现的是团结协作,应该是比较理想的状态,如果人人都能够少一些算计、多一点互帮互助,那该有多好啊?班主任不是资本家,班级更应该提倡团队精神,学生之间不应该竞争,应该互相协作!本文以团结协作为出发点,讲解线程的合作执行。
为了适应线程协作的场景,我们需要先搞明白下面的几个关键点:
1、怎样启动线程
2、任务用什么样的格式保存
3、任务全部认领完成以后,怎样关闭线程
作为班主任,有一个植树活动的新任务,需要班级3位同学共同完成,要求:每位同学随机领取0个以上的任务。
一、启动3个线程,用于模拟3个学生
go func() { fmt.Println("张三") }() go func() { fmt.Println("李四") }() go func() { fmt.Println("王五") }()
二、定义1个用于保存任务的容器
type Task struct { mu sync.Mutex items []string } func NewTask() *Task { return &Task{} } func (t *Task) Add(item string) { t.mu.Lock() defer t.mu.Unlock() t.items = append(t.items, item) } func (t *Task) Remove() string { t.mu.Lock() defer t.mu.Unlock() if len(t.items) == 0 { return "" } index := rand.Intn(len(t.items)) item := t.items[index] t.items = append(t.items[:index], t.items[index+1:]...) return item }
三、让线程消费容器里的任务,容器空的时候,表示任务已经认领完毕
这里我们用到了context.WithCancel,通俗的讲,这是一组带有取消开关的信道组的Context,执行取消后,这一组信道会收到Done信号,我们把这3个线程视为一组,用这个Context进行承载,这样,在学生认领完容器的所有任务后,通过取消功能,就可以结束掉所有线程。
package main import ( "context" "fmt" "math/rand" "strings" "sync" "time" ) type Task struct { mu sync.Mutex items []string } func NewTask() *Task { return &Task{} } func (t *Task) Add(item string) { t.mu.Lock() defer t.mu.Unlock() t.items = append(t.items, item) } func (t *Task) Remove() string { t.mu.Lock() defer t.mu.Unlock() if len(t.items) == 0 { return "" } index := rand.Intn(len(t.items)) item := t.items[index] t.items = append(t.items[:index], t.items[index+1:]...) return item } func main() { task := NewTask() task.Add("买树苗") task.Add("挖坑") task.Add("栽树苗") task.Add("回填") task.Add("培土") task.Add("施肥") task.Add("浇水") var wg sync.WaitGroup ctx, cancel := context.WithCancel(context.Background()) wg.Add(3) go func() { defer wg.Done() for { select { case <- ctx.Done(): fmt.Println("张三:退出") return default: item := task.Remove() if !IsEmptyString(item) { fmt.Printf("张三:%s\n", item) } time.Sleep(time.Second) } } }() go func() { defer wg.Done() for { select { case <- ctx.Done(): fmt.Println("李四:退出") return default: item := task.Remove() if !IsEmptyString(item) { fmt.Printf("李四:%s\n", item) } time.Sleep(time.Second) } } }() go func() { defer wg.Done() for { select { case <- ctx.Done(): fmt.Println("王五:退出") return default: item := task.Remove() if !IsEmptyString(item) { fmt.Printf("王五:%s\n", item) } time.Sleep(time.Second) } } }() time.Sleep(3 * time.Second) cancel() wg.Wait() } func IsEmptyString(str string) bool { trimmedStr := strings.TrimSpace(str) return trimmedStr == "" }
来看下,运行的效果: