From a140a9b9c7b5b9f56016733ab0eb6bfbb1563769 Mon Sep 17 00:00:00 2001 From: jshiwam Date: Fri, 29 Jul 2022 23:18:47 +0530 Subject: [PATCH 1/2] Added example for go routines --- examples/goroutines/foo.py | 17 ++++++ examples/goroutines/main.go | 118 ++++++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 examples/goroutines/foo.py create mode 100644 examples/goroutines/main.go diff --git a/examples/goroutines/foo.py b/examples/goroutines/foo.py new file mode 100644 index 0000000..9f87896 --- /dev/null +++ b/examples/goroutines/foo.py @@ -0,0 +1,17 @@ +import sys + +def print_odds(limit=10): + """ + Print odds numbers < limit + """ + for i in range(limit): + if i%2: + sys.stderr.write("odds: {}\n".format(i)) + +def print_even(limit=10): + """ + Print even numbers < limit + """ + for i in range(limit): + if i%2 == 0: + sys.stderr.write("even: {}\n".format(i)) diff --git a/examples/goroutines/main.go b/examples/goroutines/main.go new file mode 100644 index 0000000..b8e296d --- /dev/null +++ b/examples/goroutines/main.go @@ -0,0 +1,118 @@ +package main + +import ( + "errors" + "log" + "os" + "runtime" + "sync" + + python3 "github.com/go-python/cpy3" +) + +func main() { + var err error + + // At the end of all, if there was an error + // prints the error and exit with the non-zero code + defer func() { + if err != nil { + log.Printf("%+v", err) + os.Exit(1) + } + }() + + // Undo all initializations made by Py_Initialize() and subsequent + defer python3.Py_Finalize() + + // Prints any python error if it was here + // no needs to call it after each single check of PyErr_Occurred() + defer python3.PyErr_Print() + + // Initialize the Python interpreter and + // since version 3.7 it also create the GIL explicitly by calling PyEval_InitThreads() + // so you don’t have to call PyEval_InitThreads() yourself anymore + python3.Py_Initialize() // create the GIL, the GIL is locked by the main thread + + if !python3.Py_IsInitialized() { + err = errors.New("error initializing the python interpreter") + return + } + + var wg sync.WaitGroup + wg.Add(2) + + fooModule := python3.PyImport_ImportModule("foo") // new reference, a call DecRef() is needed + if fooModule == nil && python3.PyErr_Occurred() != nil { + err = errors.New("error importing the python module") + return + } + defer fooModule.DecRef() + + odds := fooModule.GetAttrString("print_odds") // new reference, a call DecRef() is needed + if odds == nil && python3.PyErr_Occurred() != nil { + err = errors.New("error getting the attribute print_odds") + return + } + defer odds.DecRef() + + even := fooModule.GetAttrString("print_even") // new reference, a call DecRef() is needed + if even == nil && python3.PyErr_Occurred() != nil { + err = errors.New("error getting the attribute print_even") + return + } + defer even.DecRef() + + limit := python3.PyLong_FromGoInt(50) // new reference, will stolen later, a call DecRef() is NOT needed + if limit == nil && python3.PyErr_Occurred() != nil { + err = errors.New("error creating python long object") + return + } + + args := python3.PyTuple_New(1) // new reference, a call DecRef() is needed + if args == nil && python3.PyErr_Occurred() != nil { + err = errors.New("error creating python tuple object") + return + } + defer args.DecRef() + + ret := python3.PyTuple_SetItem(args, 0, limit) // steals reference to limit + + // Cleans the Go variable, because now a new owner is caring about related PyObject + // no action, such as a call DecRef(), is needed here + limit = nil + + if ret != 0 { + err = errors.New("error setting a tuple item") + limit.DecRef() + limit = nil + return + } + + // Save the current state and release the GIL + // so that goroutines can acquire it + state := python3.PyEval_SaveThread() // release the GIL, the GIL is unlocked for using by goroutines + + go func() { + runtime.LockOSThread() + _gstate := python3.PyGILState_Ensure() // acquire the GIL, the GIL is locked by the 1st goroutine + odds.Call(args, python3.PyDict_New()) + python3.PyGILState_Release(_gstate) // release the GIL, the GIL is unlocked for using by others + + wg.Done() + }() + + go func() { + runtime.LockOSThread() + _gstate := python3.PyGILState_Ensure() // acquire the GIL, the GIL is locked by the 2nd goroutine + even.Call(args, python3.PyDict_New()) + python3.PyGILState_Release(_gstate) // release the GIL, the GIL is unlocked for using by others + + wg.Done() + }() + + wg.Wait() + + // Restore the state and lock the GIL + python3.PyEval_RestoreThread(state) // acquire the GIL, the GIL is locked by the main thread +} From 7ea9bc7f8982cf5b4a7953d598df1ad6689a1378 Mon Sep 17 00:00:00 2001 From: jshiwam Date: Tue, 16 Aug 2022 12:06:41 +0530 Subject: [PATCH 2/2] resolved suggested changes --- examples/goroutines/main.go | 94 +++++++++++++++++++------------------ 1 file changed, 48 insertions(+), 46 deletions(-) diff --git a/examples/goroutines/main.go b/examples/goroutines/main.go index b8e296d..a6e042d 100644 --- a/examples/goroutines/main.go +++ b/examples/goroutines/main.go @@ -3,31 +3,46 @@ package main import ( "errors" "log" - "os" "runtime" "sync" python3 "github.com/go-python/cpy3" ) -func main() { - var err error +var wg sync.WaitGroup + +func isPyErr(val *python3.PyObject) bool { + + if val == nil && python3.PyErr_Occurred() != nil { + python3.PyErr_Print() + return true + } + + return false +} + +func callPyFunc(pyFunc *python3.PyObject, args *python3.PyObject, kwargs *python3.PyObject) { + runtime.LockOSThread() - // At the end of all, if there was an error - // prints the error and exit with the non-zero code - defer func() { - if err != nil { - log.Printf("%+v", err) - os.Exit(1) - } - }() + _gstate := python3.PyGILState_Ensure() // acquire the GIL, the GIL is locked by the 1st goroutine + defer python3.PyGILState_Release(_gstate) // release the GIL, the GIL is unlocked for using by others + ret := pyFunc.Call(args, kwargs) + if isPyErr(ret) { + return + } + defer ret.DecRef() + + wg.Done() +} + +func main() { // Undo all initializations made by Py_Initialize() and subsequent defer python3.Py_Finalize() // Prints any python error if it was here // no needs to call it after each single check of PyErr_Occurred() - defer python3.PyErr_Print() + // defer python3.PyErr_Print() // Initialize the Python interpreter and // since version 3.7 it also create the GIL explicitly by calling PyEval_InitThreads() @@ -35,81 +50,68 @@ func main() { python3.Py_Initialize() // create the GIL, the GIL is locked by the main thread if !python3.Py_IsInitialized() { - err = errors.New("error initializing the python interpreter") + log.Printf("%+v", errors.New("error initializing the python interpreter")) return } - var wg sync.WaitGroup - wg.Add(2) - fooModule := python3.PyImport_ImportModule("foo") // new reference, a call DecRef() is needed - if fooModule == nil && python3.PyErr_Occurred() != nil { - err = errors.New("error importing the python module") + if isPyErr(fooModule) { return } defer fooModule.DecRef() odds := fooModule.GetAttrString("print_odds") // new reference, a call DecRef() is needed - if odds == nil && python3.PyErr_Occurred() != nil { - err = errors.New("error getting the attribute print_odds") + if isPyErr(odds) { return } defer odds.DecRef() even := fooModule.GetAttrString("print_even") // new reference, a call DecRef() is needed - if even == nil && python3.PyErr_Occurred() != nil { - err = errors.New("error getting the attribute print_even") + if isPyErr(even) { return } defer even.DecRef() limit := python3.PyLong_FromGoInt(50) // new reference, will stolen later, a call DecRef() is NOT needed - if limit == nil && python3.PyErr_Occurred() != nil { - err = errors.New("error creating python long object") + if limit == nil { + log.Printf("%+v", errors.New("error creating python long object")) return } args := python3.PyTuple_New(1) // new reference, a call DecRef() is needed - if args == nil && python3.PyErr_Occurred() != nil { - err = errors.New("error creating python tuple object") + if args == nil { + log.Printf("%+v", errors.New("error creating python tuple object")) return } defer args.DecRef() ret := python3.PyTuple_SetItem(args, 0, limit) // steals reference to limit - + if ret != 0 { + log.Printf("%+v", errors.New("error setting a tuple item")) + limit.DecRef() + limit = nil + return + } // Cleans the Go variable, because now a new owner is caring about related PyObject // no action, such as a call DecRef(), is needed here limit = nil - if ret != 0 { - err = errors.New("error setting a tuple item") - limit.DecRef() - limit = nil + kwargs := python3.PyDict_New() + if kwargs == nil { + log.Printf("%+v", errors.New("error initializing kwargs in callPyFunc ")) return } + defer kwargs.DecRef() // Save the current state and release the GIL // so that goroutines can acquire it state := python3.PyEval_SaveThread() // release the GIL, the GIL is unlocked for using by goroutines - go func() { - runtime.LockOSThread() - _gstate := python3.PyGILState_Ensure() // acquire the GIL, the GIL is locked by the 1st goroutine - odds.Call(args, python3.PyDict_New()) - python3.PyGILState_Release(_gstate) // release the GIL, the GIL is unlocked for using by others - - wg.Done() - }() + wg.Add(2) - go func() { - runtime.LockOSThread() - _gstate := python3.PyGILState_Ensure() // acquire the GIL, the GIL is locked by the 2nd goroutine - even.Call(args, python3.PyDict_New()) - python3.PyGILState_Release(_gstate) // release the GIL, the GIL is unlocked for using by others + go callPyFunc(odds, args, kwargs) - wg.Done() - }() + go callPyFunc(even, args, kwargs) wg.Wait()