I was looking to implement a similar functionality and came across this question. Not sure if you're still looking for a solution, but I found one :)
It turns out uilive can very well be used to update multiple lines, by making use of the Newline() function on a *uilive.Writer. Editing the example on their repository to write the download progress on multiple lines, we get:
writer := uilive.New() // writer for the first line writer2 := writer.Newline() // writer for the second line // start listening for updates and render writer.Start() for i := 0; i <= 100; i++ { fmt.Fprintf(writer, "Downloading File 1.. %d %%\n", i) fmt.Fprintf(writer2, "Downloading File 2.. %d %%\n", i) time.Sleep(time.Millisecond * 5) } fmt.Fprintln(writer, "Finished downloading both files :)") writer.Stop() // flush and stop rendering
For implementing something to track the progress of multiple concurrently running tasks, as @blami's answer also states, a good approach is to have a separate goroutine that handles all terminal output.
If you're interested, I've implemented this exact functionality in mllint, my tool for linting machine learning projects. It runs all its underlying linters (i.e. tasks) in parallel and prints their progress (running / done) in the manner that you describe in your question. See this file for the implementation of that. Specifically, the printTasks function reads from the list of tasks in the system and prints each task's display name and status on a new line.
Note that especially with multi-line printing, it becomes important to have control over flushing the print buffer, as you do not want your writer to automatically flush halfway through writing an update to the buffer. Therefore, I set the writer's RefreshDuration to something large (e.g. time.Hour) and call Flush() on the writer after printing all lines, i.e. at the end of printTasks.