ソースを参照

Add concurrency example

Tal Liron 5 年 前
コミット
d5a78ca5a6
4 ファイル変更115 行追加27 行削除
  1. 1 1
      README.md
  2. 2 2
      examples/hello-world/api/functions.go
  3. 6 0
      examples/hello-world/foo.py
  4. 106 24
      examples/hello-world/main.go

+ 1 - 1
README.md

@@ -75,7 +75,7 @@ be aware of:
   Go code will thus need to explicitly call `Release` on all Python references to ensure that they are
   Go code will thus need to explicitly call `Release` on all Python references to ensure that they are
   garbage collected. Luckily, the `defer` keyword makes this easy enough in many circumstances.
   garbage collected. Luckily, the `defer` keyword makes this easy enough in many circumstances.
 * Concurrency is a bit tricky in Python due to its infamous Global Interpreter Lock (GIL). If
 * Concurrency is a bit tricky in Python due to its infamous Global Interpreter Lock (GIL). If
-  you are calling Python code from a Goroutine make sure to call `python.SaveThreadState` and/or
+  you are calling Python code from a Goroutine make sure to call `python.SaveThreadState` and
   `python.EnsureGilState` as appropriate. See the examples for more detail.
   `python.EnsureGilState` as appropriate. See the examples for more detail.
 
 
 
 

+ 2 - 2
examples/hello-world/api/functions.go

@@ -7,10 +7,10 @@ import (
 )
 )
 
 
 func sayGoodbye() {
 func sayGoodbye() {
-	fmt.Println("Go >> goodbye from Go!")
+	fmt.Println("Go >> Goodbye from Go!")
 }
 }
 
 
 func concat(a string, b string) string {
 func concat(a string, b string) string {
-	fmt.Printf("Go >> concatenating %q and %q\n", a, b)
+	fmt.Printf("Go >> Concatenating %q and %q\n", a, b)
 	return a + " " + b
 	return a + " " + b
 }
 }

+ 6 - 0
examples/hello-world/foo.py

@@ -17,6 +17,12 @@ def say_name():
 def say_name_fast():
 def say_name_fast():
     print("Python >> The name is " + api.concat_fast("Tal", "Liron"))
     print("Python >> The name is " + api.concat_fast("Tal", "Liron"))
 
 
+size = 0
+
+def grow(count):
+    global size
+    size += count
+
 class Person:
 class Person:
     def __init__(self, name):
     def __init__(self, name):
         self.name = name
         self.name = name

+ 106 - 24
examples/hello-world/main.go

@@ -2,66 +2,148 @@ package main
 
 
 import (
 import (
 	"fmt"
 	"fmt"
+	"sync"
 
 
 	"github.com/tliron/py4go"
 	"github.com/tliron/py4go"
 	"github.com/tliron/py4go/examples/hello-world/api"
 	"github.com/tliron/py4go/examples/hello-world/api"
 )
 )
 
 
-func main() {
-	python.PrependPythonPath(".")
-
-	python.Initialize()
-	defer python.Finalize()
-
+func version() {
 	fmt.Printf("Go >> Python version:\n%s\n", python.Version())
 	fmt.Printf("Go >> Python version:\n%s\n", python.Version())
-	fmt.Println()
+}
 
 
+func typeChecking() {
 	fmt.Println("Go >> Type checking:")
 	fmt.Println("Go >> Type checking:")
 	float, _ := python.NewPrimitiveReference(1.0)
 	float, _ := python.NewPrimitiveReference(1.0)
 	fmt.Printf("Go >> IsFloat: %t\n", float.IsFloat())
 	fmt.Printf("Go >> IsFloat: %t\n", float.IsFloat())
-	fmt.Println()
-
-	api, _ := api.CreateModule()
-	api.EnableModule()
-	defer api.Release()
-
-	foo, _ := python.Import("foo")
-	defer foo.Release()
+}
 
 
+func callPythonFunction(module *python.Reference) {
 	fmt.Println("Go >> Calling a Python function:")
 	fmt.Println("Go >> Calling a Python function:")
-	hello, _ := foo.GetAttr("hello")
+
+	hello, _ := module.GetAttr("hello")
 	defer hello.Release()
 	defer hello.Release()
+
 	r, _ := hello.Call("Tal")
 	r, _ := hello.Call("Tal")
 	defer r.Release()
 	defer r.Release()
+
 	r_, _ := r.ToString()
 	r_, _ := r.ToString()
 	fmt.Printf("Go >> Python function returned: %s\n", r_)
 	fmt.Printf("Go >> Python function returned: %s\n", r_)
-	fmt.Println()
+}
 
 
+func callPythonMethod(module *python.Reference) {
 	fmt.Println("Go >> Calling a Python method:")
 	fmt.Println("Go >> Calling a Python method:")
-	person, _ := foo.GetAttr("person")
+
+	person, _ := module.GetAttr("person")
 	defer person.Release()
 	defer person.Release()
+
 	greet, _ := person.GetAttr("greet")
 	greet, _ := person.GetAttr("greet")
 	defer greet.Release()
 	defer greet.Release()
+
 	greet.Call()
 	greet.Call()
-	fmt.Println()
+}
 
 
+func getPythonException(module *python.Reference) {
 	fmt.Println("Go >> Python exception as Go error:")
 	fmt.Println("Go >> Python exception as Go error:")
-	bad, _ := foo.GetAttr("bad")
+
+	bad, _ := module.GetAttr("bad")
 	defer bad.Release()
 	defer bad.Release()
+
 	if _, err := bad.Call(); err != nil {
 	if _, err := bad.Call(); err != nil {
 		fmt.Printf("Go >> Error message: %s\n", err)
 		fmt.Printf("Go >> Error message: %s\n", err)
 	}
 	}
-	fmt.Println()
+}
 
 
-	goodbye, _ := foo.GetAttr("goodbye")
+func callGoFromPython(module *python.Reference) {
+	goodbye, _ := module.GetAttr("goodbye")
 	defer goodbye.Release()
 	defer goodbye.Release()
 	goodbye.Call()
 	goodbye.Call()
 
 
-	sayName, _ := foo.GetAttr("say_name")
+	sayName, _ := module.GetAttr("say_name")
 	defer sayName.Release()
 	defer sayName.Release()
 	sayName.Call()
 	sayName.Call()
 
 
-	sayNameFast, _ := foo.GetAttr("say_name_fast")
+	sayNameFast, _ := module.GetAttr("say_name_fast")
 	defer sayNameFast.Release()
 	defer sayNameFast.Release()
 	sayNameFast.Call()
 	sayNameFast.Call()
 }
 }
+
+func concurrency(module *python.Reference) {
+	fmt.Println("Go >> Concurrency:")
+
+	grow, _ := module.GetAttr("grow")
+	defer grow.Release()
+
+	func() {
+		// Release Python's lock on our main thread, allowing other threads to execute
+		// (Without this our calls to "grow", from other threads, will block forever)
+		ts := python.SaveThreadState()
+		defer ts.Restore()
+
+		// Parallel work:
+
+		var wg sync.WaitGroup
+		defer wg.Wait()
+
+		for i := 0; i < 5; i++ {
+			wg.Add(1)
+
+			go func() {
+				defer wg.Done()
+
+				for i := 0; i < 100; i++ {
+					// We must manually acquire Python's Global Interpreter Lock (GIL),
+					// because Python doesn't know about our Go "threads" (goroutines)
+					gs := python.EnsureGilState()
+					defer gs.Release()
+
+					// (Note: We could also have acquired the GIL outside of this for-loop;
+					// it's really up to us, how we want to balance concurrency with the cost
+					// of context switching)
+
+					grow.Call(1)
+				}
+			}()
+		}
+	}()
+
+	size, _ := module.GetAttr("size")
+	defer size.Release()
+
+	size_, _ := size.ToInt64()
+	fmt.Printf("Go >> Size is %d\n", size_)
+}
+
+func main() {
+	python.PrependPythonPath(".")
+
+	python.Initialize()
+	defer python.Finalize()
+
+	version()
+	fmt.Println()
+
+	typeChecking()
+	fmt.Println()
+
+	api, _ := api.CreateModule()
+	defer api.Release()
+	api.EnableModule()
+
+	foo, _ := python.Import("foo")
+	defer foo.Release()
+
+	callPythonFunction(foo)
+	fmt.Println()
+
+	callPythonMethod(foo)
+	fmt.Println()
+
+	getPythonException(foo)
+	fmt.Println()
+
+	callGoFromPython(foo)
+	fmt.Println()
+
+	concurrency(foo)
+}