Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Node.js C++ Addon: Threading

Tags:

c++

node.js

I'm writing a GUI addon for Node.js (wxWidgets) and I want to run the GUI loop in an own thread as I don't think it would be a good idea to merge it with Node's main thread and event loop.

However I'm not sure how to create a new thread. I got it running with uv_queue_work(). But it will not create an exclusive thread for the GUI but use Node's thread pool. And this might be a bad idea since the worker will stay during the whole runtime. (Not sure about this)

I could also use wxWidgets' wxThread, works, too. And I found a new function uv_thread_create in libuv git master. No idea how to use that as there is no description and besides it is not yet available in Node.js stable build.

My question: What is the "standard" way to create a multi-threaded Node.js Addon, if any? I looked at other projects but could only find short-running worker threads using libuv.

like image 914
Eliasdx Avatar asked Jun 15 '12 12:06

Eliasdx


1 Answers

The answer is that you typically want to use the background threads managed by Nodejs by submitting your work to the uv event queue, and then let nodejs worry about how to create and manage threads.

below is the boilerplate example which is missing from the node.js v0.10 manual.

struct Baton
{
    // we need this structure to interact with the uv
    // the uv_work_t must be the initial element and should store 
    // the callback function to be useful, but the rest
    // is user defined depending on what is needed to actually do the work.
    uv_work_t                    request;
    v8::Persistent<v8::Function> callback;
    // Add more elements to the structure as needed
    int                          countdown;
};


static void AsyncTestWork (uv_work_t* req);
static void AsyncTestAfter(uv_work_t* req,int status);
static Handle<Value> AsyncTestPrep(const Arguments& args) 
{
    HandleScope scope;
    if (args.Length() != 1) {
        ThrowException(Exception::TypeError(String::New("Wrong number of arguments -- needs (callback)")));
        return scope.Close(Undefined());
    }

    if (!args[0]->IsFunction()) {
        ThrowException(Exception::TypeError(String::New("Wrong type of arguments -- needs (callback)")));
        return scope.Close(Undefined());
    }

    v8::Local<v8::Function> callback = v8::Local<v8::Function>::Cast(args[0]);

    Baton* baton = new Baton();
    baton->request.data = baton;
    baton->callback = v8::Persistent<v8::Function>::New(callback);
    baton->countdown = 3;

    uv_queue_work(uv_default_loop(), &baton->request, AsyncTestWork, AsyncTestAfter);

    return scope.Close(v8::Undefined());
}


static void AsyncTestWork (uv_work_t* req)
{
    // This method will run in a seperate thread where you can do 
    // your blocking background work.
    // In this function, you cannot under any circumstances access any V8/node js
    // valiables -- all data and memory needed, MUSt be in the Baton structure
    Baton* baton = static_cast<Baton*>(req->data);
    sleep(6); // some fictional work, delaying the return....
    baton->countdown -= 1;  // my actual work in this 
}

static void AsyncTestAfter(uv_work_t* req,int status)
{
    // This is what is called after the 'Work' is done, you can now move any data from 
    // Baton to the V8/Nodejs space and invoke call back functions

    Baton* baton = static_cast<Baton*>(req->data);

    v8::Handle<v8::Value> argv1[] = { v8::Null(), Number::New(baton->countdown) };
    v8::Handle<v8::Value> argv2[] = { v8::Null(), Number::New(23) };

    v8::TryCatch try_catch;
       // Call back to the JS function, you can make as many/few callbacks  
       // as you need, they just go on the event loop queue for now.
       // Note: that it is mostly customary to call the callback  
       // function just (exactly) which is probably what you want  
       // to do to avoid confusing your users if you make a public api
       baton->callback->Call(v8::Context::GetCurrent()->Global(), 2, argv1);
       baton->callback->Call(v8::Context::GetCurrent()->Global(), 2, argv2);
    if (try_catch.HasCaught()) {
        node::FatalException(try_catch);
    }

    if (baton->countdown > 0) {
        // resubmit the worker for yet more work
        uv_queue_work(uv_default_loop(), &baton->request, AsyncTestWork, AsyncTestAfter);
    } else {
        // we are finished, clean up and be done
        baton->callback.Dispose();
        delete baton;
    }
}


void init(Handle<Object> exports) 
{

  exports->Set(String::NewSymbol("myAsyncTestFunction"),
               FunctionTemplate::New(AsyncTestPrep)->GetFunction());

}
like image 68
Soren Avatar answered Nov 09 '22 00:11

Soren