ARTICLES
Fun with threading, mutexes and managing threads
By me
Do i really need threads?
You have lived without it so far. Sure, you don’t really need threads and you can continue to code your single-thread application. Then why do i need threads? Answer to this question is very simple - 30-40 years ago technology was different, simple, easier. Adapting to new things can be difficult, yes, but also challenging. Always be willing to learn new stuff or ask yourself is this is what you want to do.
How to do it in Go?
It’s increadibly easy, thanks Google!
Tools they develop and way to use threads feels like native.
In C it was a lot different, you can see this text about it with examples: https://www.geeksforgeeks.org/multithreading-c-2/.
So you use pthread_create with pointer set to function that will run on separate thread. In go you would just run go mythread()
and function mythread will run in seaprate thread. Yes, that easy!
Native approach
There’s a two ways you can run new thread: by providing function/method or by implementing function in same line. Take a look at this:
package main
import (
"fmt"
"time"
)
func mythread() {
time.Sleep(1)
fmt.Println("Thread has ended")
}
func main() {
go mythread()
time.Sleep(2)
}
https://play.golang.org/p/Fnyi1kweNWB
This is probably most simple example of this. Thread waits for 1 second, writes “Thread has ended” and exits. Main function is waiting for 2 second - if we didn’t wait for 2 second program will immediatelly exit and thread will be killed as parent process just die. Other alternatives to this are WaitGroups. There’s a few things that defines thread life cycle:
- Add 1 to waitgroups
- Spawn thread
- Do something in thread
- Call waitgroups.Done() (defer)
- Wait until waitgroups.Wait() is done
Channels
There’s also channels.
Channels is used to send messages from one thread to another. Remember, main function is also another thread.
When you’re receiving message it blocks (until message is received). This can also be done withotu blocking using select. It’s also better to use select when dealing with multiple message types.
Here’s an example of using channels: Channels. And in this example msg := <-messages
is blocking until thread send message. You probably notice <-
which is used for sending or receiving message. Using this message is going from right side to left.
Here’s also an example of select: Select.
When waiting for some data keep in mind that go scheduler needs to be run (it usually runs on function exit). You can also manuall call garbage collector with runtime.GC().
Mutex
Mutex is a thread lock that will pass only one thread at the time. Resource that has mutex is locked by one thread while another thread waits for unlock. This can lead to inefficiency as those threads are idling. If you’re using spinlocks mutex will not block but it will periodically check if lock is released. Spinlocks are not natively supported but you can use custom code: Spinlock.
Racing conditions
Racing conditions are situations where two threads tries to access same data at same time and result of application will be unpredictable because it depends on which thread got data first. This is solved by using mutex or channels - threads will wait for locks or data to be released/available and this is why Go is so great!
Existing solutions
I had a bit of exploring how people did their threading manager and i have stumble upon this: Tunny. Tunny is a very interesting threading pool manager with thread states, spawn pool control (amount of threads) and worker implementation. There’s an example how to async serve web requests by adding new worker/job at the pool. There’s also very simple example of go routines with states by implementing tunny.Worker. You can also set pool size (how many thread you want to run at the same time).
File thread locking cane be implemented using Flock go library. If your threads are tryint to read or/and write files you can use this library to actually lock specific file so only one thread will be able to access specific file. It also provides TryLock function for non-blocking operations.