Search⌘ K

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()
   
...