简单的代码却有三个结果,求助(使用goland V2020.1)

请大侠不吝赐教,先谢过。

代码如下(赋值粘贴到main.go即可运行):

package main

import (
“bufio”
“fmt”
“os”
“strings”
)

func main() {
var userInput = “”
go UserInput(&userInput)
for {
if “exit” == userInput {
fmt.Print(“程序退出”)
os.Exit(0)
}
}
}

func UserInput(userInput *string) {
reader := bufio.NewReader(os.Stdin)
for {
fmt.Print(“输入指令:”)
text, _ := reader.ReadString(‘\n’)
*userInput = strings.Replace(text, “\n”, “”, -1) // 去掉回车换行符
switch *userInput {
case “exit”: { // 退出本程序
return
}
default:
fmt.Println(“不可识别的指令”)
}
}

}

【1】通过“调试”按钮来运行程序:

输入exit并回车后,程序正常识别exit指令,程序正常退出。

【2】通过“运行”按钮来运行程序:

输入exit并回车后,显示下面结果(报告panic):

【3】通过终端,运行exe文件,显示下面结果(不能识别输入指令):

我的问题:
(1)为什么三种方式运行,会有三种结果?
(2)为什么“调试”模式运行,一切OK,而“运行”模式和“终端exe”模式都不对?特别是“终端exe”模式运行的结果,怎么会跑到switch的default分支去?

最佳答案

原因应该是指针变量的赋值不是原子操作,实验代码如下

package main

import (
    "fmt"
    "strings"
    "sync"
)

func main() {

    for i:=0;i<100;i++{
        do1() // 有概率触发空指针
        // do2()  在指针变量读写的地方加锁。不会空指针
        // 上面证明了指针变量的赋值不是原子操作,内部应该有个置为空,然后再放值的过程
    }
}

func UserInput1(p *string) {
    for {
        text:= "exit"
        *p = strings.Replace(text, "\n", "", -1) // 去掉回车换行符
        switch *p {
        case "exit": { // 退出本程序e
            return
        }
        default:
            fmt.Println("不可识别的指令")
        }
    }
}

func do1()  {
    var userInput = ""
    go UserInput1(&userInput)
    for {
        a:=userInput
        if "exit" == a {
            fmt.Print ("程序退出")
            return
        }
    }
}



var m sync.Mutex
func UserInput2(p *string) {
    for {
        text:= "exit"
        m.Lock()
        *p = strings.Replace(text, "\n", "", -1) // 去掉回车换行符
        m.Unlock()
        switch *p {
        case "exit": { // 退出本程序e
            return
        }
        default:
            fmt.Println("不可识别的指令")
        }
    }
}

func do2()  {
    var userInput = ""
    go UserInput2(&userInput)
    for {
        m.Lock()
        a:=userInput
        m.Unlock()
        if "exit" == a {
            fmt.Print ("程序退出")
            return
        }

    }
}
2年前 评论
michael8908 (楼主) 2年前
讨论数量: 7
package main

import (
"bufio"
"fmt"
"os"
"strings"
)

func main() {
    var userInput = ""
    go UserInput(&userInput)
    for {
        if "exit" == userInput {
            fmt.Print ("程序退出")
            os.Exit(0)
        }
    }
}

func UserInput(userInput *string) {
    reader := bufio.NewReader(os.Stdin)
    for {
        fmt.Print("输入指令:")
        text, _ := reader.ReadString('\n')
        *userInput = strings.Replace(text, "\n", "", -1) // 去掉回车换行符
        switch *userInput {
        case "exit": { // 退出本程序
        return
        }
        default:
            fmt.Println("不可识别的指令")
        }
    }
}
2年前 评论

启动一个新的协程时,协程的调用会立即返回。与函数不同,程序控制不会去等待 Go 协程执行完毕。在调用 Go 协程之后,程序控制会立即返回到代码的下一行,忽略该协程的任何返回值。 如果希望运行其他 Go 协程,Go 主协程必须继续运行着。如果 Go 主协程终止,则程序终止,于是其他 Go 协程也不会继续运行。

func main() {
    var userInput = ""
    //time.Sleep(1 * time.Second)
    go UserInput(&userInput)
    for {
        if "exit" == userInput {
            fmt.Print ("程序退出")
            os.Exit(0)
        }
    }
}
2年前 评论
michael8908 (楼主) 2年前

三个结果不同的原因

1.调试

  • 环境 : 调试的断点大概率是打在 func UserInput内
  • 原因 : 主线程未中断(main)

2.运行

  • 环境 : 运行环境,楼主想通过主线程一直循坏来使主线程不中断
  • 原因 : 同CR(二楼),主线程开启协程后,主线程未结束时,主线程和协程形成一个竞争关系,是指针问题。PS:没有遇到楼主的指针问题

3.终端

  • 环境 : 正常环境
  • 原因 : 输入错误,前面输了一个空格(空格)exit,可能不太严谨

优化方案(个人:不使用指针)

package main

import (
    "bufio"
    "fmt"
    "os"
    "strings"
    "sync"
)

//使用一个计数器
var group = sync.WaitGroup{}

func main() {
    //当需要开启一个线程时,设定等待的线程+1
    group.Add(1)
    go UserInput()

    //等待全部线程结束
    group.Wait()
    os.Exit(0)
}

func UserInput() {
    reader := bufio.NewReader(os.Stdin)
    for {
        fmt.Print("输入指令:")
        text, _ := reader.ReadString('\n')
        text = strings.Replace(text,"\n", "",-1)
        switch text {
        case "exit":
            { // 退出本程序
                fmt.Print ("程序退出\n")
                //线程计数-1
                group.Done()
                return
            }
        default:
            fmt.Println("不可识别的指令")
        }
    }
}
2年前 评论
michael8908 (楼主) 2年前

有没有人能讲明白为什么userInput会变成空指针 :neutral_face:

2年前 评论
michael8908 (楼主) 2年前

原因应该是指针变量的赋值不是原子操作,实验代码如下

package main

import (
    "fmt"
    "strings"
    "sync"
)

func main() {

    for i:=0;i<100;i++{
        do1() // 有概率触发空指针
        // do2()  在指针变量读写的地方加锁。不会空指针
        // 上面证明了指针变量的赋值不是原子操作,内部应该有个置为空,然后再放值的过程
    }
}

func UserInput1(p *string) {
    for {
        text:= "exit"
        *p = strings.Replace(text, "\n", "", -1) // 去掉回车换行符
        switch *p {
        case "exit": { // 退出本程序e
            return
        }
        default:
            fmt.Println("不可识别的指令")
        }
    }
}

func do1()  {
    var userInput = ""
    go UserInput1(&userInput)
    for {
        a:=userInput
        if "exit" == a {
            fmt.Print ("程序退出")
            return
        }
    }
}



var m sync.Mutex
func UserInput2(p *string) {
    for {
        text:= "exit"
        m.Lock()
        *p = strings.Replace(text, "\n", "", -1) // 去掉回车换行符
        m.Unlock()
        switch *p {
        case "exit": { // 退出本程序e
            return
        }
        default:
            fmt.Println("不可识别的指令")
        }
    }
}

func do2()  {
    var userInput = ""
    go UserInput2(&userInput)
    for {
        m.Lock()
        a:=userInput
        m.Unlock()
        if "exit" == a {
            fmt.Print ("程序退出")
            return
        }

    }
}
2年前 评论
michael8908 (楼主) 2年前

终端运行到 default 我也闹不明白,可能是输入的编码方式问题,golang里面默认的字符串是utf-8 ,可能你的终端输入的不是utf-8 ? 我是mac,没有复现这个现象

2年前 评论
michael8908 (楼主) 2年前

debug可能是golang的debug工具有特殊处理,这个我不懂

2年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!