Skip to content
This repository has been archived by the owner on Aug 10, 2022. It is now read-only.

Goroutine calls Python but deadlocks #49

Closed
xwjahahahaha opened this issue Oct 11, 2021 · 2 comments
Closed

Goroutine calls Python but deadlocks #49

xwjahahahaha opened this issue Oct 11, 2021 · 2 comments

Comments

@xwjahahahaha
Copy link

xwjahahahaha commented Oct 11, 2021

Describe what happened:

Does the project support Goroutine?
Recently, I encountered the following problems in the network programming project (simplified version):

  • directory structure:

.
├── go.mod
├── go.sum
├── hello
│   ├── model
│   │   └── resnet18.pth
│   └── test.py
├── main.go
└── test

  • target test.py file :
import torch
from torchvision import models


def Test(m):
    print("Call test function")
    model = models.resnet18(pretrained=True)
    if m == "save":
        torch.save(model, "./hello/model/resnet18.pth")
        return "model saved."
    else:
        return torch.load("./hello/model/resnet18.pth")

main.go:

import (
	"fmt"
	"github.com/datadog/go-python3"
	"os"
)

func init() {
	python3.Py_Initialize()
	if !python3.Py_IsInitialized() {
		fmt.Println("Error initializing the python interpreter")
		os.Exit(1)
	}
}

func main() {
	flagChan := make(chan bool , 1)
	testModule := ImportModule("./hello", "test")
	Test := testModule.GetAttrString("Test")
	args := python3.PyTuple_New(1)
	input := python3.PyUnicode_FromString("save")
	python3.PyTuple_SetItem(args, 0, input)
	res := Test.Call(args, python3.Py_None)
	repr, _ := pythonRepr(res)
	fmt.Println("[Main FUNC] res =", repr)
	go func() {
		fmt.Println("goroutine start...")
		args := python3.PyTuple_New(1)
		defer args.DecRef()
		input := python3.PyUnicode_FromString("load")
		python3.PyTuple_SetItem(args, 0, input)
		res := Test.Call(args, python3.Py_None)
		repr, _ := pythonRepr(res)
		fmt.Println("[Goroutine FUNC] res =", repr)
		fmt.Println("goroutine over")
		flagChan <- true
	}()
	<- flagChan
	args.DecRef()
	Test.DecRef()
	testModule.DecRef()
        python3.Py_Finalize()
	fmt.Println("main routine over")
	select {}
}

func ImportModule(dir, name string) *python3.PyObject {
	sysModule := python3.PyImport_ImportModule("sys") 	// import sys
	defer sysModule.DecRef()
	path := sysModule.GetAttrString("path")            // path = sys.path
	defer path.DecRef()
	dirObject := python3.PyUnicode_FromString(dir)
	defer dirObject.DecRef()
	python3.PyList_Insert(path, 0, dirObject) // path.insert(0, dir)
	return python3.PyImport_ImportModule(name)            // return __import__(name)
}

func pythonRepr(o *python3.PyObject) (string, error) {
	if o == nil {
		return "", fmt.Errorf("object is nil")
	}
	s := o.Repr()		
	if s == nil {
		python3.PyErr_Clear()
		return "", fmt.Errorf("failed to call Repr object method")
	}
	defer s.DecRef()
	return python3.PyUnicode_AsUTF8(s), nil
}

When I run it, sometimes it works perfectly, but sometimes it blocks python code:

Call test function
[Main FUNC] res = 'model saved.'
goroutine start...
Call test function
(It's always blocked here...)

Describe what you expected:

Call test function
[Main FUNC] res = 'model saved.'
goroutine start...
Call test function
[Goroutine FUNC] res = ResNet(....)
goroutine over
main routine over

I don't know if this is a problem with multiple goroutine resource preemption or not supporting multithreading, or maybe it's a problem with my go-python3 code.

Because I was writing a network communication project, I had to have multiple goroutine receive and send data (Model) enabled on the same node, and the entire python3 environment remained running until the node was shut down. I'm limited in what I can do, so I'd appreciate it if you could take a look

@xwjahahahaha xwjahahahaha changed the title Goroutine calls Python pytblock deadlocks Goroutine calls Python but deadlocks Oct 11, 2021
@christian-korneck
Copy link
Contributor

As far as I understand, this project is just a relatively thin wrapper around Python's C API. So basically all the limitations of the Python C API apply. One limitation is that Python has a global interpreter lock (GIL). You can't do multiple things in a Python interpreter at the same time (unless you use Python's concurrency features in the Python code that you're executing).

The easiest way to make embedded Python to work with goroutines is to make sure that only one goroutine is actually "doing" Python at a time and all the other ones have to wait. Here's an excellent blog post and related conference talk that cover this and a few other options.

@xwjahahahaha
Copy link
Author

Thank you very much indeed.
The method and reasons is introduced in article blog post:

package main

import (
    "sync"

    "github.com/sbinet/go-python"
)

func main() {
    // The following will also create the GIL explicitly
    // by calling PyEval_InitThreads(), without waiting
    // for the interpreter to do that
    python.Initialize()

    var wg sync.WaitGroup
    wg.Add(2)

    fooModule := python.PyImport_ImportModule("foo")
    odds := fooModule.GetAttrString("print_odds")
    even := fooModule.GetAttrString("print_even")

    // Initialize() has locked the the GIL but at this point we don't need it
    // anymore. We save the current state and release the lock
    // so that goroutines can acquire it
    state := python.PyEval_SaveThread()

    go func() {
        _gstate := python.PyGILState_Ensure()
        odds.Call(python.PyTuple_New(0), python.PyDict_New())
        python.PyGILState_Release(_gstate)

        wg.Done()
    }()

    go func() {
        _gstate := python.PyGILState_Ensure()
        even.Call(python.PyTuple_New(0), python.PyDict_New())
        python.PyGILState_Release(_gstate)

        wg.Done()
    }()

    wg.Wait()

    // At this point we know we won't need Python anymore in this
    // program, we can restore the state and lock the GIL to perform
    // the final operations before exiting.
    python.PyEval_RestoreThread(state)
    python.Finalize()
}

There are three steps:

  1. Save the state and lock the GIL.
  2. Do Python.
  3. Restore the state and unlock the GIL.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants