将 shared_ptr 传递给 Lua

Passing shared_ptr to Lua

前段时间我决定采用引用计数方法来管理资源的生命周期,并且它成功了。后来我遇到了一个新的挑战,要保留一个对象的生命周期,直到 Lua 在一个单独的线程中执行一个 long-运行 任务(这是通过 turbo 库实现的)。

我的想法是将 shared_ptr 的副本传递给 Lua 并让垃圾收集器决定何时不再需要该资源。 但问题是 Lua 两次清除数据,我不知道为什么。

在这里我将描述我执行的几个步骤。首先,我正在创建一个 table,为其设置 metatable 并将其设置为 global

static const struct luaL_reg FileTableDefinition[] = {
  { "__gc",     destroy },
  { NULL,       NULL } };

const char* id = "PointerManager";
lua_newtable( state );
luaL_newmetatable( state, id );
luaL_register( state, NULL, FileTableDefinition );
lua_pushliteral( state, "__index" );
lua_pushvalue( state, -2 );
lua_rawset( state, -3 );
lua_setglobal( state, id );

在第二步中,我正在为用户数据中的一个对象分配一个 shared_ptr,并从第 2 步中定义的 table 设置一个析构函数。 该文件是一个 class 导出到 LUA 因此用户实际上可以执行对该 class.

的调用
void *userData = lua_newuserdata( state, sizeof( std::shared_ptr<File> ) );

// Handle allocation failure
if ( ! userData) { return; }

// Use the placement parameters of the new operator to allocate
// shared_ptr within the userdata provided by Lua. Copy our other
// shared_ptr into it, increasing the reference count
new(userData) std::shared_ptr<File>( aFile );

// Now just set the metatable on this new object
luaL_getmetatable( state, "PointerManager" );
lua_setmetatable( state, -2 );

对象的析构函数定义如下:

int destroy( lua_State* state )
{
    void* resourcePtr = luaL_checkudata( state, 1, "PointerManager" );  
    if( resourcePtr )
    {
        auto resource = static_cast<std::shared_ptr<File>*>( resourcePtr );
        resource->reset();
        return 1;
    }
    return 0;
}

我正在执行事件的脚本调用 (lua_pcall)。脚本定义如下:

local turbo = require("turbo")

function sleep(n)
  os.execute("sleep " .. tonumber(n))
end

function on_file_event ( file )
    print("on_file_event enter.")
    turbo.ioloop.instance():add_callback(function()
        local thread = turbo.thread.Thread(function(th)
            print("executing")
            sleep(1)
            print( file:filepath() )
            file = nil
            collectgarbage("collect")
            print("stopped executing")
            th:stop()
        end)

        turbo.ioloop.instance():close()
    end):start()
    print("on_file_event exit.")
end

我将 cout 放入 class 析构函数中,不知何故它被调用了两次。两次调用 destroy 函数(一次是在我调用垃圾收集器时,一次是在程序退出时),并且无论我是否正在重置底层对象的共享指针析构函数,都达到了两次。共享指针和指针本身的内存是一样的,线程id也是一样的。我也进行了一次 Valgrind 潜水,但没有发现损坏。

Calling destroy file 
        >> ~File object deleted (/opt/nids/artifacts/generic/32). Address: 0x1bec850

Calling destroy file 
        >> ~File object deleted (/opt/nids/artifacts/generic/32). Address: 0x1bec850

我做错了什么吗?

找到原因了。它是 turbo 库内部的一个底层分支。当我在 lua 中创建一个新线程时 - 发生了 C 分支,所以地址是相同的,但实际上它们在不同的内存中 space。后来内存被释放,出现了两套印刷品。

我能够用 straces 确认这一点。