Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added example for go routines #21

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions examples/goroutines/foo.py
Original file line number Diff line number Diff line change
@@ -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))
120 changes: 120 additions & 0 deletions examples/goroutines/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package main

import (
"errors"
"log"
"runtime"
"sync"

python3 "github.com/go-python/cpy3"
)

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

_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()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that's safe. The docs say:

Call this function only when the error indicator is set. Otherwise it will cause a fatal error!

// 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() {
log.Printf("%+v", errors.New("error initializing the python interpreter"))
return
}

fooModule := python3.PyImport_ImportModule("foo") // new reference, a call DecRef() is needed
if isPyErr(fooModule) {
return
}
defer fooModule.DecRef()

odds := fooModule.GetAttrString("print_odds") // new reference, a call DecRef() is needed
if isPyErr(odds) {
return
}
defer odds.DecRef()

even := fooModule.GetAttrString("print_even") // new reference, a call DecRef() is needed
if isPyErr(even) {
return
}
defer even.DecRef()

limit := python3.PyLong_FromGoInt(50) // new reference, will stolen later, a call DecRef() is NOT needed
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again the docs aren't clear if this causes a PyErr. Only check for nil?

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 {
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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this cleanup is too early? In the next block you're still using limit (to decref in case PyTuple_SetItem has failed).


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

wg.Add(2)

go callPyFunc(odds, args, kwargs)

go callPyFunc(even, args, kwargs)

wg.Wait()

// Restore the state and lock the GIL
python3.PyEval_RestoreThread(state) // acquire the GIL, the GIL is locked by the main thread
}