Tasks and Worker Processes
This lesson lists the possible ways of communication and their pros and cons in different situations.
We'll cover the following...
Suppose we have to perform several tasks; a task is performed by a worker (process). A Task
can be defined as a struct (the concrete details are not important here):
type Task struct {
// some state
}
1st paradigm: use shared memory to synchronize
The pool of tasks is shared memory. To synchronize the work and to avoid race conditions, we have to guard the pool with a Mutex
lock:
type Pool struct {
Mu sync.Mutex
Tasks []Task
}
A sync.Mutex
(as mentioned earlier in the course) is a mutual exclusion lock. It serves to guard the entrance to a critical section in code. Only one goroutine (thread) can enter that section at one time. If more than one goroutine is allowed, a race-condition can exist, which means the Pool
struct can no longer be updated correctly.
In the traditional model (applied in most classic OO-languages like C++, Java, and C#) the Worker
process could be coded as:
func Worker(pool *Pool) {
for {
pool.Mu.Lock()
// begin critical section:
task := pool.Tasks[0] // take the first task
pool.Tasks = pool.Tasks[1:] // update the pool of tasks
// end critical section
pool.Mu.Unlock()
...