参考:
https://github.com/howeyc/fsnotify
https://beego.me/docs/install/bee.md
bee 使用fsnotify包,使用kqueue检查文件变化,根据修改时间戳,每s检查一次,如果有变化就重新编译,调用路径如下:
beego/bee/bee.go
var commands = []*Command{
cmdNew,
cmdRun,
…
}
func main() {
…
for _, cmd := range commands {
…
os.Exit(cmd.Run(cmd, args))
…
}
beego/bee/run.go
var cmdRun = &Command{
…
}
func init() {
cmdRun.Run = runApp
…
}
func runApp(cmd *Command, args []string) int {
…
crupath, _ := os.Getwd()
…
gps := GetGOPATHs()
…
NewWatcher(paths, files, true)
Autobuild(files, true)
…
}
beego/bee/watch.go
func NewWatcher(paths []string, files []string, isgenerate bool) {
watcher, err := fsnotify.NewWatcher()
select {
case e := <-watcher.Event:
mt := getFileModTime(e.Name) …
Autobuild(files, isgenerate)
}
}
func Autobuild(files []string, isgenerate bool) {
appName := appname
if runtime.GOOS == “windows” {
appName += “.exe”
}
args := []string{“build”}
args = append(args, “-o”, appName)
if buildTags != “” {
args = append(args, “-tags”, buildTags)
}
args = append(args, files…)
bcmd := exec.Command(cmdName, args…)
bcmd.Env = append(os.Environ(), “GOGC=off”)
bcmd.Stdout = os.Stdout
bcmd.Stderr = os.Stderr
err = bcmd.Run()
}
howeyc/fsnotify/fsnotify_bsd.go
func NewWatcher() (*Watcher, error) {
fd, errno := syscall.Kqueue()
if fd == -1 {
return nil, os.NewSyscallError(“kqueue”, errno)
}
w := &Watcher{
kq: fd,
watches: make(map[string]int),
fsnFlags: make(map[string]uint32),
enFlags: make(map[string]uint32),
paths: make(map[int]string),
finfo: make(map[int]os.FileInfo),
fileExists: make(map[string]bool),
externalWatches: make(map[string]bool),
internalEvent: make(chan *FileEvent),
Event: make(chan *FileEvent),
Error: make(chan error),
done: make(chan bool, 1),
}
go w.readEvents()
go w.purgeEvents()
return w, nil
}
说明几个概念, struct event, kevent()和kqueue。
struct event就是kevent()操作的最基本的事件结构。
kevent() 是一个系统调用syscall,而kqueue是freebsd内核中的一个事件队列kernel queue。
kevent()是kqueue的用户界面,是对kqueue进行添加,删除操作的用户态的界面。
===
下面就重点介绍一下struct event和kevent()这两个开发者必须要了解的参数和API。
常见的I/O模型:
blocking I/O
nonblocking I/O
I/O multiplexing (select and poll)
signal driven I/O (SIGIO)
asynchronous I/O (the POSIX aio_functions)—————异步IO模型最大的特点是 完成后发回通知。
阻塞与否,取决于实现IO交换的方式。
异步阻塞是基于select,select函数本身的实现方式是阻塞的,而采用select函数有个好处就是它可以同时监听多个文件句柄.
异步非阻塞直接在完成后通知,用户进程只需要发起一个IO操作然后立即返回,等IO操作真正的完成以后,应用程序会得到IO操作完成的通知,此时用户进程只需要对数据进行处理就好了,不需要进行实际的IO读写操作,因为真正的IO读取或者写入操作已经由内核完成了。
select和iocp分别对应第3种与第5种模型,那么epoll与kqueue呢?其实也于select属于同一种模型,只是更高级一些,可以看作有了第4种模型的某些特性,如callback机制。
为什么epoll,kqueue比select高级?
答案是,他们无轮询。因为他们用callback取代了。想想看,当套接字比较多的时候,每次select()都要通过遍历FD_SETSIZE个Socket来完成调度,不管哪个Socket是活跃的,都遍历一遍。这会浪费很多CPU时间。如果能给套接字注册某个回调函数,当他们活跃时,自动完成相关操作,那就避免了轮询,这正是epoll与kqueue做的。
windows or *nix (IOCP or kqueue/epoll)?
诚然,Windows的IOCP非常出色,目前很少有支持asynchronous I/O的系统,但是由于其系统本身的局限性,大型服务器还是在UNIX下。而且正如上面所述,kqueue/epoll 与 IOCP相比,就是多了一层从内核copy数据到应用层的阻塞,从而不能算作asynchronous I/O类。但是,这层小小的阻塞无足轻重,kqueue与epoll已经做得很优秀了。
提供一致的接口,IO Design Patterns
实际上,不管是哪种模型,都可以抽象一层出来,提供一致的接口,广为人知的有ACE,Libevent(基于reactor模式)这些,他们都是跨平台的,而且他们自动选择最优的I/O复用机制,用户只需调用接口即可。说到这里又得说说2个设计模式,Reactor and Proactor。见:Reactor模式–VS–Proactor模式。Libevent是Reactor模型,ACE提供Proactor模型。实际都是对各种I/O复用机制的封装。
只有IOCP是asynchronous I/O,其他机制或多或少都会有一点阻塞。
select低效是因为每次它都需要轮询。但低效也是相对的,视情况而定,也可通过良好的设计改善
epoll, kqueue、select是Reacor模式,IOCP是Proactor模式。