Tasks and Worker Processes
This lesson lists the possible ways of communication and their pros and cons in different situations.
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()
...