Jooarye
Jooarye

Reputation: 27

How can i detect a deadlock?

I just recently started learning go and wanted to test my skills by writing a program that calculates all amicable and perfect numbers. Sadly I'm having issues with my code! If anyone knows how I can stop the program after all numbers are calculated, preferably without an error, please let me know.

package main

import (
    "fmt"
    "strings"
)

func getDivisorSum(number int) int {
    divisors := 0

    for possibleDivisor := 1; possibleDivisor <= number / 2; possibleDivisor++ {
        if number % possibleDivisor == 0 {
            divisors += possibleDivisor
        }
    }

    return divisors
}

func checkNumber(number int, channel chan string, done *int) {
    first := getDivisorSum(number)

    if first == number {
        channel <- fmt.Sprintf("%d", number)
        *done += 1
        return
    }

    second := getDivisorSum(first)

    if number == second {
        channel <- fmt.Sprintf("%d:%d", number, first)
        *done += 1
        return
    }
}

func checkNumberRange(min int, max int) {
    channel := make(chan string)
    done := 0

    for number := min; number <= max; number++ {
        go checkNumber(number, channel, &done)
    }

    for {
        tmp := <- channel

        if strings.Contains(tmp, ":") {
            parts := strings.Split(tmp, ":")
            fmt.Printf("%s is an amicable of %s!\n", parts[0], parts[1])
        } else {
            fmt.Printf("%s is perfect!\n", tmp)
        }
    }
}

func main() {
    checkNumberRange(1, 65536)
}

At the moment the program is crashing with a deadlock at

tmp := <- channel

Thanks in advance, Jooarye!

Upvotes: 1

Views: 662

Answers (3)

Ferdy
Ferdy

Reputation: 1201

Reading from a channel is blocked until there is data. You can check the second return value to assert whether the channel was closed. Use close(channel) when finished writing.

tmp, ok := <-channel

You have an endless for-loop forever reading the channel.

Upvotes: -1

Burak Serdar
Burak Serdar

Reputation: 51572

There are multiple problems with your program:

  • The use of done is racy. Multiple goroutines are writing to it without synchronization. It doesn't look like it's been used.
  • You are trying to read from a channel in an infinite for-loop. Eventually, you read from a channel when there are no goroutines left, and that's the deadlock.

One way to deal with this is to use a sync.WaitGroup, and keep track of starting/stopping goroutines that way.

wg:=sync.WaitGroup{}
for number := min; number <= max; number++ {
    wg.Add(1)
    go checkNumber(number, channel, &wg)
}

In checkNumber:

func checkNumber(number int, channel chan string,wg *wg.WaitGroup) {
  defer wg.Done()
  ...
}

You can have a separate goroutine that waits for all goroutines to complete:

go func() {
  wg.Wait()
  close(chan)
}()

And, in your for-loop, read it like this, so it terminates when the channel closes:

for tmp:=range chan {
  ...
}

Upvotes: 7

Denis Sterligov
Denis Sterligov

Reputation: 66

I advise you to read the following article https://blog.golang.org/pipelines.

But I don't think that channels are a good choice in this case. Perhaps it would be better to use sync.WaitGroup

wg := &sync.WaitGroup{}
wg.Add(max)
for number := min; number <= max; number++ {
    go func(number int) {
        defer wg.Done()
        res := checkNumber(number)
        if strings.Contains(res, ":") {
            parts := strings.Split(res, ":")
            fmt.Printf("%s is an amicable of %s!\n", parts[0], parts[1])
        } else {
            fmt.Printf("%s is perfect!\n", res)
        }
    }(number)
}
wg.Wait()

Upvotes: 0

Related Questions