-
Notifications
You must be signed in to change notification settings - Fork 52
Added example for go routines #21
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
base: master
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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)) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think that's safe. The docs say:
|
||
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would move this down right before the Goroutines start, for better readability, etc. |
||
|
||
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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure if a failed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @christian-korneck failed
Here is the output:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've asked in Python IRC what the cannonical way to do error handling is and was pointed to this: "You normally don’t need to call Looking at large open source projects it seems like people are using a lot of different variants. But just checking for (I still need to check a few things, but probably will update my examples). |
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Even I am not sure on this one, for |
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
|
||
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() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would it be better to pass |
||
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()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this call will return a PyObject (of type |
||
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 | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we need to do this error check with a defer? Wouldn't it maybe simpler and more idiomatic to wrap most part of
main()
in a new function that returns an error? And then call it from my main and check for an error there?