musicfox 2.0——golang重写版

musicfox 2.0终于完成了,没有太多的功能变更,主要使用golang进行重写,优化了用户体验(主要是Windows下)。

存在的问题与方案

musicfox最开始是用Dart写的,工作原理大致是主进程fork一个mpg123的子进程,主进程处理UI渲染、用户交互及网络请求等,然后通过进程间管道通信,来控制mpg123子进程的音乐播放、暂停以及播放器的音量大小等。

这种模式下,会存在以下这些问题:

  • 依赖mpg123,需要用户手动安装mpg123
  • mpg123只能播放mp3音乐,网易云无损音质是flac格式,无法播放
  • 进程间需要通过管道进行通信,Windows下不容易处理
  • Dart大多数三方包都是基于Flutter的,原生包不多
  • ...

为了解决这些问题,于是开始采用golang进行重写,优势主要是:

  • golang支持更多平台的二进制编译
  • 拥有丰富的第三方包,例如音频播放的beep、TUI渲染的
    bubbletea、本地数据库的bolt等等
  • 不需要借助其他进程,所以不存在进程通信
  • 支持mp3、flac、wav等格式音频文件的解码播放
  • goroutine + chan

另外对我自己来说可以深入熟悉golang...

使用go时遇到的问题

协程泄漏

在使用golang过程中遇到比较严重的问题就是协程泄漏。例如下面的代码:

package main

import (
	"fmt"
	"runtime"
)

func main() {
	c1 := make(chan struct{})
	for i := 0; i < 10; i++ {
		go test(c1)
		fmt.Println(runtime.NumGoroutine())
	}
}

func test(c chan<- struct{}) {
	c <- struct{}{}
}

执行后会输出:

2
3
4
5
6
7
8
9
10
11

可见,协程数一直在增加,这是因为没有消费者消费chan中的数据,所有子协程都阻塞在写chan的操作。

类似这种情况就会导致协程数不断增长,出现协程泄漏,内存占用不断上升。

如何尽量避免协程泄漏

  1. 确保chan都会有相应的消费者、生产者,且生产速度和消费速度差不多
  2. 如果需要往chan中压数据或读数据,但又不确定是否有消费者或生产者,可以使用select结构避免阻塞
  3. 使用goroutine + chan处理,避免创建大量协程,例如:
package main

import (
	"fmt"
	"runtime"
	"time"
)

func main() {
	var data [100]string
	// 获取data逻辑
	for _, v := range data {
		go test(v)
	}

	fmt.Println(runtime.NumGoroutine())
}

func test(s string) {
	// 假装业务逻辑
	time.Sleep(time.Millisecond)
}

输出为: 101,也就是新开了100个协程,如果数据更多时,会产生更多协程。这种情况可以考虑创建固定数量的若干个协程,然后通过chan将数据投递给子协程进行处理,避免大量协程占用内存,如:

package main

import (
	"fmt"
	"runtime"
	"time"
)

func main() {
	var data [100]string
	c := make(chan string)

	for i := 0; i < 5; i++ {
		go test(c)
	}

	for _, v := range data {
		c <- v
	}

	fmt.Println(runtime.NumGoroutine())
}

func test(c <-chan string) {
	for _ = range c {
        // 业务逻辑
		time.Sleep(time.Millisecond)
	}
}

协程泄漏排查

  1. runtime.NumGoroutine()查看协程数
  2. 使用gops排查,可以查看当前协程数、调用栈等

预览

preview1
preview2

安装

Mac

提供两种方式安装:

使用brew安装

brew tap anhoder/go-musicfox && brew install go-musicfox

如果你之前安装过musicfox,需要使用下列命令重新链接:

brew unlink musicfox && brew link --overwrite go-musicfox

直接下载

下载Mac可执行文件,在iTerm或Terminal中打开

Linux

身边没有Linux设备,而播放音乐依赖CGO,无法进行交叉编译,所以暂时没有可用的二进制文件

Windows

下载Windows可执行文件,在命令行中运行即可。

推荐使用Windows Terminal,UI及体验会好很多

快捷键

按键 作用 备注
h/H/LEFT
l/L/RIGHT
k/K/UP
j/J/DOWN
q/Q 退出
space 暂停/播放
[ 上一曲
] 下一曲
- 减小音量
= 加大音量
n/N/ENTER 进入选中的菜单
b/B/ESC 返回上级菜单
w/W 退出并退出登录
p 切换播放方式
P 心动模式(仅在歌单中时有效)
r/R 重新渲染UI Windows调整窗口大小后,没有事件触发,可以使用该方法手动重新渲染
, 喜欢当前播放歌曲
< 喜欢当前选中歌曲
. 当前播放歌曲移除出喜欢
> 当前选中歌曲移除出喜欢
/ 标记当前播放歌曲为不喜欢
? 标记当前选中歌曲为不喜欢

TODO