记录一下多线程模型,最近有相关 内容的开发,做一下简单总结
本篇记录传统select/poll
这是一个简单的demo,模仿select/poll 原理
Go
// 用Go模拟select的用法
package main
import (
"fmt"
"net"
"os"
"syscall"
)
func main() {
// 创建3种不同的文件描述符
// 1. TCP连接
conn, _ := net.Dial("tcp", "google.com:80")
tcpFd := getFd(conn)
// 2. 标准输入(终端)
stdinFd := os.Stdin.Fd()
// 3. 管道
pipeR, pipeW, _ := os.Pipe()
pipeFd := pipeR.Fd()
// 准备监控这些fd
var readFds syscall.FdSet
FD_SET(tcpFd, &readFds)
FD_SET(stdinFd, &readFds)
FD_SET(pipeFd, &readFds)
// 找出最大的fd编号
maxFd := int(pipeFd)
if tcpFd > maxFd {
maxFd = int(tcpFd)
}
if stdinFd > maxFd {
maxFd = int(stdinFd)
}
// 等待任何fd有数据
n, err := syscall.Select(maxFd+1, &readFds, nil, nil, nil)
if err != nil {
panic(err)
}
fmt.Printf("有 %d 个文件描述符就绪\n", n)
// 检查哪个fd有数据
if FD_ISSET(tcpFd, &readFds) {
fmt.Println("网络数据到达")
// 读取socket数据,不会阻塞
}
if FD_ISSET(stdinFd, &readFds) {
fmt.Println("键盘输入到达")
// 读取终端输入,不会阻塞
}
if FD_ISSET(pipeFd, &readFds) {
fmt.Println("管道数据到达")
// 读取管道数据,不会阻塞
}
}
func FD_SET(fd uintptr, set *syscall.FdSet) {
set.Bits[fd/64] |= 1 << (fd % 64)
}
func FD_ISSET(fd uintptr, set *syscall.FdSet) bool {
return (set.Bits[fd/64] & (1 << (fd % 64))) != 0
}
func getFd(conn net.Conn) uintptr {
tcpConn := conn.(*net.TCPConn)
file, _ := tcpConn.File()
return file.Fd()
}
上面这个只适合旧版本的golang,新版本syscall.Select会被弃用,而且还有语法错误,mac环境
windows + go1.20如下
Go
// 用Go模拟select的用法 - 跨平台版本
package main
import (
"fmt"
"io"
"net"
"os"
"time"
)
func main() {
// 创建3种不同的可读源
// 1. TCP连接
conn, err := net.DialTimeout("tcp", "google.com:80", 5*time.Second)
if err != nil {
fmt.Printf("连接失败: %v\n", err)
conn = nil
} else {
defer conn.Close()
// 发送一个简单的HTTP请求
fmt.Fprintf(conn, "GET / HTTP/1.0\r\n\r\n")
}
// 2. 标准输入(使用channel模拟)
stdinCh := make(chan []byte)
go func() {
buf := make([]byte, 1024)
n, err := os.Stdin.Read(buf)
if err == nil {
stdinCh <- buf[:n]
} else if err != io.EOF {
close(stdinCh)
}
}()
// 3. 管道(使用channel模拟)
pipeCh := make(chan []byte)
go func() {
// 模拟2秒后管道有数据
time.Sleep(2 * time.Second)
pipeCh <- []byte("hello from pipe\n")
}()
// 使用 Go 的 select 语句来监控多个 channel
fmt.Println("等待数据到达...")
fmt.Println("提示:你可以在2秒内输入一些内容,或等待管道数据到达")
fmt.Println()
// 设置超时
timeout := time.After(10 * time.Second)
select {
case data := <-stdinCh:
fmt.Println("✓ 键盘输入到达")
fmt.Printf(" 键盘输入: %s", data)
case data := <-pipeCh:
fmt.Println("✓ 管道数据到达")
fmt.Printf(" 管道数据: %s", data)
case <-timeout:
fmt.Println("✗ 超时,没有数据到达")
}
// 检查网络连接是否有数据(非阻塞)
if conn != nil {
// 设置1秒超时读取
conn.SetReadDeadline(time.Now().Add(1 * time.Second))
buf := make([]byte, 1024)
n, err := conn.Read(buf)
if err == nil && n > 0 {
fmt.Println("✓ 网络数据到达")
fmt.Printf(" 网络数据: %s\n", buf[:n])
} else if err != nil {
fmt.Printf("网络读取: %v\n", err)
}
}
}
Go
连接失败: dial tcp 142.250.69.174:80: i/o timeout
等待数据到达...
提示:你可以在2秒内输入一些内容,或等待管道数据到达
✓ 管道数据到达
管道数据: hello from pipe