├── .gitignore ├── LICENSE ├── README.md ├── Thread.go ├── Thread_test.go └── old └── proof-of-concept └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | .*.sw? 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Liam Zdenek 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | go-pthreads 2 | =========== 3 | 4 | This is a binding of C's pthreads to Google Go. **This library is not a 5 | replacement for goroutines.** This library is designed to help bind C libraries 6 | with blocking function calls to Go in a go-friendly manner. If this is not your 7 | use case, this library probably won't help you. 8 | 9 | Use Case 10 | -------- 11 | 12 | If a goroutine exists that calls a function that will block potentially 13 | indefinitely, that goroutine cannot be stopped until the blocking function 14 | returns and the goroutine checks an "exit" channel, and exits of its own will. 15 | 16 | In every day go programming, this condition should not exist, as all inter- 17 | thread communication, as well as reading and writing, should be done using 18 | channels. However, in C, many libraries (and, in my specific case, networking 19 | libraries) implement blocking functions (recv), and the mixture of a blocking 20 | function and a multi-channel select caused many implementation problems and 21 | "hacks" to get the "blocking" function to return periodically, so the exit 22 | channel could be checked, and the routine could exit if it had to. 23 | 24 | Example 25 | ------- 26 | 27 | ```go 28 | package main; 29 | 30 | import ( 31 | "github.com/liamzdenek/go-pthreads" 32 | "fmt" 33 | ) 34 | 35 | func main() { 36 | thread := pthread.Create(func() { 37 | // we're within the pthread 38 | counter := 1; 39 | for { 40 | // time to make a blocking function call. The library includes 41 | // pthread.sleep for demo purposes, but this will work with any 42 | // library that causes IO wait 43 | 44 | // an example using github.com/alecthomas/gozmq can be found in 45 | // Thread_test.go 46 | 47 | fmt.Printf("Hello, %d\n", counter) 48 | counter++ 49 | pthread.Sleep(1) // seconds 50 | } 51 | }) 52 | 53 | // within the main goroutine 54 | pthread.Sleep(3) 55 | 56 | fmt.Printf("Killing thread\n"); 57 | thread.Kill() 58 | 59 | pthread.Sleep(3); 60 | } 61 | ``` 62 | 63 | Output: 64 | ``` 65 | $ time go run test.go 66 | Hello, 1 67 | Hello, 2 68 | Hello, 3 69 | Killing thread 70 | go run test.go 0.28s user 0.09s system 5% cpu 6.421 total 71 | ``` 72 | 73 | Pros/Cons 74 | --------- 75 | 76 | Pros: 77 | 78 | * Provides a mechanism to kill a blocked thread (thread.Kill()) 79 | * Provides thread status without any logic in the child (thread.Running()) 80 | 81 | Cons: 82 | 83 | * Does not implement pthread_cleanup_push/pop 84 | * Runs in a dedicated thread (most of the time; sometimes this is a pro) 85 | * Does not integrate with the go scheduler (as a consequence of the new thread) 86 | * Harder to debug (crashes in C code don't produce stack traces) 87 | 88 | -------------------------------------------------------------------------------- /Thread.go: -------------------------------------------------------------------------------- 1 | package pthread 2 | 3 | /* 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | extern void createThreadCallback(); 10 | static void sig_func(int sig); 11 | 12 | static void createThread(pthread_t* pid) { 13 | pthread_create(pid, NULL, (void*)createThreadCallback, NULL); 14 | } 15 | 16 | static void sig_func(int sig) 17 | { 18 | //printf("handling exit signal\n"); 19 | signal(SIGSEGV,sig_func); 20 | pthread_exit(NULL); 21 | } 22 | 23 | static void register_sig_handler() { 24 | signal(SIGSEGV,sig_func); 25 | } 26 | */ 27 | import "C" 28 | import "unsafe" 29 | 30 | type Thread uintptr 31 | type ThreadCallback func() 32 | 33 | var create_callback chan ThreadCallback 34 | 35 | func init() { 36 | C.register_sig_handler() 37 | create_callback = make(chan ThreadCallback, 1) 38 | } 39 | 40 | //export createThreadCallback 41 | func createThreadCallback() { 42 | C.register_sig_handler() 43 | C.pthread_setcanceltype(C.PTHREAD_CANCEL_ASYNCHRONOUS, nil) 44 | (<-create_callback)() 45 | } 46 | 47 | // calls C's sleep function 48 | func Sleep(seconds uint) { 49 | C.sleep(C.uint(seconds)) 50 | } 51 | 52 | // initializes a thread using pthread_create 53 | func Create(cb ThreadCallback) Thread { 54 | var pid C.pthread_t 55 | pidptr := &pid 56 | create_callback <- cb 57 | 58 | C.createThread(pidptr) 59 | 60 | return Thread(uintptr(unsafe.Pointer(&pid))) 61 | } 62 | 63 | // determines if the thread is running 64 | func (t Thread) Running() bool { 65 | // magic number "3". oops 66 | // couldn't figure out the proper way to do this. probably because i suck 67 | // if someone knows the right way, pls submit a pull request 68 | return int(C.pthread_kill(t.c(), 0)) != 3 69 | } 70 | 71 | // signals the thread in question to terminate 72 | func (t Thread) Kill() { 73 | C.pthread_kill(t.c(), C.SIGSEGV) 74 | } 75 | 76 | // helper function to convert the Thread object into a C.pthread_t object 77 | func (t Thread) c() C.pthread_t { 78 | return *(*C.pthread_t)(unsafe.Pointer(t)) 79 | } 80 | -------------------------------------------------------------------------------- /Thread_test.go: -------------------------------------------------------------------------------- 1 | package pthread 2 | 3 | import ( 4 | "github.com/alecthomas/gozmq" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func Test_ZMQ(t *testing.T) { 10 | // initialize some ZMQ crap 11 | failed := false 12 | context, _ := gozmq.NewContext() 13 | socket, _ := context.NewSocket(gozmq.REP) 14 | socket.Bind("tcp://127.0.0.1:9898") 15 | 16 | // create the thread 17 | thread := Create(func() { 18 | // this call should block forever, and thread.Kill() should stop it 19 | socket.Recv(0) 20 | 21 | // this should never happen 22 | failed = true 23 | }) 24 | 25 | // wait around for a bit 26 | time.Sleep(time.Millisecond * 100) 27 | 28 | // make sure the thread started 29 | if !thread.Running() { 30 | t.Error("The thread was not running after starting it") 31 | } 32 | 33 | // stop the thread 34 | thread.Kill() 35 | 36 | // wait around a bit more 37 | time.Sleep(time.Millisecond * 100) 38 | 39 | // make sure the thread terminated 40 | if thread.Running() { 41 | t.Error("The thread was still running after the kill signal") 42 | } 43 | 44 | // finally, make sure that the thread didn't go to a point in execution that 45 | // it was not supposed to reach 46 | if failed { 47 | // this might be more revealing of a bug in zmq or pthreads, but what do 48 | // i know 49 | t.Error("The recv call did not block or the thread did not exit properly.") 50 | } 51 | 52 | } 53 | 54 | func Test_ManyThreads(t *testing.T) { 55 | literal := func() { 56 | for { 57 | Sleep(1); // sleep for 1s, forever 58 | } 59 | } 60 | 61 | // start up the threads 62 | thread1 := Create(literal); 63 | thread2 := Create(literal); 64 | 65 | // give them some time to spin up 66 | time.Sleep(time.Millisecond * 100); 67 | 68 | // ensure both started 69 | if !thread1.Running() || !thread2.Running() { 70 | t.Error("One or both of the threads failed to start properly"); 71 | } 72 | 73 | // kill one of the threads 74 | thread1.Kill(); 75 | 76 | // give it some time to clean up 77 | time.Sleep(time.Millisecond * 100); 78 | 79 | // ensure one has exited and the other has running 80 | if thread1.Running() { 81 | t.Error("Thread 1 has failed to stop"); 82 | } 83 | if !thread2.Running() { 84 | t.Error("Thread 2 stopped when it shouldn't have"); 85 | } 86 | 87 | // stop the second thread 88 | thread2.Kill(); 89 | 90 | // wait for it to spin down 91 | time.Sleep(time.Millisecond * 100); 92 | 93 | // ensure that it has spun down 94 | if thread2.Running() { 95 | t.Error("Thread 2 has failed to stop"); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /old/proof-of-concept/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | /* 4 | #include 5 | 6 | extern void callMe(); 7 | 8 | static void doCallMe(pthread_t* pid, pthread_attr_t* attr) { 9 | pthread_create(pid,attr,(void*)callMe,NULL); 10 | } 11 | */ 12 | import "C" 13 | import "fmt" 14 | import "time" 15 | 16 | //export callMe 17 | func callMe() { 18 | time.Sleep(time.Second); 19 | fmt.Printf("In the C thread\n"); 20 | } 21 | 22 | func main() { 23 | var attr C.pthread_attr_t 24 | var pid C.pthread_t 25 | 26 | C.pthread_attr_init(&attr) 27 | C.doCallMe(&pid, &attr); 28 | 29 | time.Sleep(time.Second*2) 30 | fmt.Printf("Bye\n") 31 | } 32 | 33 | --------------------------------------------------------------------------------