使用go-routines的递归并发代码死锁

时间:2016-09-19 12:10:10

标签: recursion go concurrency deadlock

我试图实现一个通用搜索算法,该算法将同时遍历任意树状数据结构(为树的每个级别启动一个go例程)。

我遇到的问题是搜索失败时(它完成整个树的行走而发现没有匹配)我最终陷入僵局,我不明白为什么。

搜索成功时,代码正常工作。

我为你的上下文添加了辅助函数,但是我的问题在于我使用的是例程,它们都在concurrentSearch()函数中。

如果您发现代码杂乱,我会提前告知。我一整天都来回尝试不同的想法。

感谢您的反馈!

这是我的代码:

package search

import "reflect"

// Search an arbitrary tree / map like data structure.
func concurrentSearch(output chan interface{}, data interface{}, key string) {
    // search() is your search algorithm.
    result := search(data, key)
    if result != nil {
        // Found something, so put it on the chan.
        // This is the point of the function.
        output <- result
        return // Success.
    }

    // Since we didn't find a match we will get a slice of the next level of values.
    iterableType := listValues(data)
    if len(iterableType) == 0 {
        return
    }

    var wg sync.WaitGroup
    for _, value := range iterableType {
        wg.Add(1)

        go func(next interface{}) {
            defer wg.Done()
            concurrentSearch(output, next, key)
        }(value)

    }
    wg.Wait()
    // Wait never finishes, so the function never returns
} 

// Returns a slice of values based on Type.
func listValues(data interface{}) []interface{} {
    value := reflect.ValueOf(data)
    values := []interface{}{}

    switch value.Kind() {
    case reflect.Map:
        for _, key := range value.MapKeys() {
            values = append(values, value.MapIndex(key).Interface())
        }

    case reflect.Slice:
        for ii := 0; ii < value.Len(); ii++ {
            values = append(values, value.Index(ii).Interface())
        }

    case reflect.Struct:
        for ii := 0; ii < value.NumField(); ii++ {
            if value.Field(ii).CanSet() {
                values = append(values, value.Field(ii).Interface())
            }
        }

    case reflect.Ptr:
        rawValue := value.Elem()
        if !rawValue.IsValid() {
            return nil
        }
        values = listValues(rawValue)

    case reflect.Interface:
        rawValue := value.Elem()
        values = listValues(rawValue)
    }

    return values
}

// search just checks the value for an index of key, otherwise returns nil.
func search(data interface{}, key string) interface{} {
    value := reflect.ValueOf(data)
    switch value.Kind() {

    // If the type is indexable, check the index.
    // Default to returning nil
    case reflect.Struct:
        for ii := 0; ii < value.NumField(); ii++ {
            field := value.Field(ii)
            // Without CanSet() reflect panics about Unexported fields.
            if field.CanSet() {
                if field.Type().Name() == key {
                    return field.Elem().Interface()
                }
            }
        }
        return nil

    case reflect.Map:
        for _, mapkey := range value.MapKeys() {
            if key == mapkey.String() {
                return value.MapIndex(mapkey).Elem().Interface()
            }
        }
        return nil

    case reflect.Slice:
        return nil

        // For pointer types we just unwrap and call again.
    case reflect.Ptr:
        rawValue := value.Elem()
        if !rawValue.IsValid() {
            return nil
        }
        return search(rawValue, key)

    case reflect.Interface:
        rawValue := value.Elem()
        return search(rawValue, key)

    case reflect.String:
        return nil

    default:
        return nil
    }

}

这是我的测试代码,遗憾的是我无法共享测试数据。它只是一堆嵌套的json对象。

package state

import (
    "encoding/json"
    "io/ioutil"
    "reflect"
    "testing"
)

const (
    dataPath = "path/to/your/data.json"
)

var tests = []struct {
    Input  string
    Result interface{}
}{
    // your test fixtures here for table driven tests.
}

type mydata struct {
    One   map[string]interface{} `json:"1"`
    Two   map[string]interface{} `json:"2"`
    Three map[string]interface{} `json:"3"`
}

func TestConcurrentSearch(t *testing.T) {
    data := &mydata{
        map[string]interface{}{},
        map[string]interface{}{},
        map[string]interface{}{},
    }
    bytes, err := ioutil.ReadFile(dataPath)
    if err != nil {
        t.Errorf("ioutil: problem loading test data, %v", err)
    }
    jerr := json.Unmarshal(bytes, data)
    if jerr != nil {
        t.Errorf("json: problem unmarshalling test data, %s", jerr)
    }

    // Setup finished

    output := make(chan interface{})
    for _, fixture := range tests {
        go concurrentSearch(output, data, fixture.Input)
        select {
        case result := <-output:
            if !reflect.DeepEqual(fixture.Result, result) {
                t.Errorf("Expected: %v, Actual %v", fixture.Result, result)
            }
        }
    }
}

0 个答案:

没有答案