如何通过并发和超时控制管理SSH的建立

51 阅读3分钟

首先需要3个线程而不是1个线程,因为1个线程在执行的过程中是阻塞的,无法判断超时,1个线程判断是否超时,最后是1个主线程接受这两个线程发出来的信号,如果是SSH执行的线程执行结束,则现将执行结束的消息告诉主现场,主线程拿着结果返回并结束,在结束前通知判断超时是否结束的线程也结束。

根据这个思路我可以先经验技术验证,首先是最简单的模拟ssh执行的函数,传入执行时间,传入结果,其次是时间超时这个函数,不好处理,如果是传入超时时间和开始时间,返回是否超时,那么在使用他之前需要一个for循环一直遍历,看是否超时,这时候我记得Go语言里面协程之间传递消息有两种方式,一种方式是通过waitgroup死锁参数的变量,另一种方式是通过通道传递,这里面为什省事我先用通道,用通道传递现在是超时了,这时候模拟SSH也是,可以得到下面的代码

func mockSSH(resultChan chan Result, runtime time.Duration)  {
    time.Sleep(runtime)
    resultChan <- Result{Code: 1}
}

func checkTimeOut(timeoutChan chan bool, timeout time.Duration) {
    endTime := time.Now().Add(timeout)
    
    for {
       if time.Now().After(endTime){
          timeoutChan <- true
          break
       }
       time.Sleep(1*time.Second)
    }
}

func main() {
    timeoutChan := make(chan bool)
    resultChan := make(chan Result)
    go checkTimeOut(timeoutChan, 10*time.Second)
    go mockSSH(resultChan, 30*time.Second)
}

然后我需要考虑主线程判断任务结束,这里用for循环,借助Go语言的select可以实现同时判断多个协程,我预期他会执行超时了,然后执行主线程执行结束,可实际结果并不是这样,是执行超时了、拿到执行结果、然后才是主线程执行结束,这个执行顺序真的好奇怪,是因为select退出机制我不理解吗,我回到官网上去看一下

timeoutChan := make(chan bool)
	resultChan := make(chan Result)
	go checkTimeOut(timeoutChan, 10*time.Second)
	go mockSSH(resultChan, 30*time.Second)

	for {
		select {
		case timeout := <-timeoutChan:
			if timeout {
				fmt.Println("超时了")
				break
			}
		case result := <-resultChan:
			fmt.Println("拿到执行结果", result)
			break
		}
	}

	fmt.Println("主线程执行结束")

官网上是select里面是return,那么我来试一下select后面跟着return,符合我一半的预期,就是执行了超时了,然后线程直接结束,看来return会让整个线程直接结束

func main() {
	timeoutChan := make(chan bool)
	resultChan := make(chan Result)
	go checkTimeOut(timeoutChan, 10*time.Second)
	go mockSSH(resultChan, 30*time.Second)

	for {
		select {
		case timeout := <-timeoutChan:
			if timeout {
				fmt.Println("超时了")
				return
			}
		case result := <-resultChan:
			fmt.Println("拿到执行结果", result)
			return
		}
	}

	fmt.Println("主线程执行结束")
}

刚才和deepseek沟通,发现一个情况,我执行了超时了,其余mockSSH和主线程并没有结束,这可能是一个隐患,也许for循环这里适合放到最后,如果我是在想要后面的主线程结束,也许可以放到defer里面也许能解决问题,发现确实如此,defer里面可以解决,这样就实现了我期待的业务逻辑

type Result struct {
	Code int
}

func mockSSH(resultChan chan Result, runtime time.Duration) {
	time.Sleep(runtime)
	resultChan <- Result{Code: 1}
}

func checkTimeOut(timeoutChan chan bool, timeout time.Duration) {
	endTime := time.Now().Add(timeout)

	for {
		if time.Now().After(endTime) {
			timeoutChan <- true
			break
		}
		time.Sleep(1 * time.Second)
	}
}

func main() {
	timeoutChan := make(chan bool)
	resultChan := make(chan Result)
	go checkTimeOut(timeoutChan, 10*time.Second)
	go mockSSH(resultChan, 30*time.Second)

	defer func() {
		fmt.Println("主线程执行结束")
	}()

	for {
		select {
		case timeout := <-timeoutChan:
			if timeout {
				fmt.Println("超时了")
				return
			}
		case result := <-resultChan:
			fmt.Println("拿到执行结果", result)
			return
		}
	}
}

这样还有可以改进的地方,就是一个线程结束的时候,另一个线程没有结束,