多线程 C Lua 模块导致 Lua 脚本中出现段错误
Multithreaded C Lua module leading to segfault in Lua script
我为 Lua 编写了一个非常简单的 C 库,它由一个启动线程的函数组成,所述线程除了循环之外什么都不做:
#include "lua.h"
#include "lauxlib.h"
#include <pthread.h>
#include <stdio.h>
pthread_t handle;
void* mythread(void* args)
{
printf("In the thread !\n");
while(1);
pthread_exit(NULL);
}
int start_mythread()
{
return pthread_create(&handle, NULL, mythread, NULL);
}
int start_mythread_lua(lua_State* L)
{
lua_pushnumber(L, start_mythread());
return 1;
}
static const luaL_Reg testlib[] = {
{"start_mythread", start_mythread_lua},
{NULL, NULL}
};
int luaopen_test(lua_State* L)
{
/*
//for lua 5.2
luaL_newlib(L, testlib);
lua_setglobal(L, "test");
*/
luaL_register(L, "test", testlib);
return 1;
}
现在,如果我编写一个非常简单的 Lua 脚本,它就可以:
require("test")
test.start_mythread()
运行 带有lua myscript.lua
的脚本有时会导致段错误。以下是 GDB 对核心转储的看法:
Program terminated with signal 11, Segmentation fault.
#0 0xb778b75c in ?? ()
(gdb) thread apply all bt
Thread 2 (Thread 0xb751c940 (LWP 29078)):
#0 0xb75b3715 in _int_free () at malloc.c:4087
#1 0x08058ab9 in l_alloc ()
#2 0x080513a2 in luaM_realloc_ ()
#3 0x0805047b in sweeplist ()
#4 0x080510ef in luaC_freeall ()
#5 0x080545db in close_state ()
#6 0x0804acba in main () at lua.c:389
Thread 1 (Thread 0xb74efb40 (LWP 29080)):
#0 0xb778b75c in ?? ()
#1 0xb74f6efb in start_thread () from /lib/i386-linux-gnu/i686/cmov/libpthread.so.0
#2 0xb7629dfe in clone () at ../sysdeps/unix/sysv/linux/i386/clone.S:129
主线程的堆栈会不时发生一些变化。
似乎 start_thread 函数想要跳转到给定地址(在本例中为 b778b75c),该地址有时恰好属于无法访问的内存。
编辑
我也有一个 valgrind 输出:
==642== Memcheck, a memory error detector
==642== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==642== Using Valgrind-3.10.0 and LibVEX; rerun with -h for copyright info
==642== Command: lua5.1 go.lua
==642==
In the thread !
In the thread !
==642== Thread 2:
==642== Jump to the invalid address stated on the next line
==642== at 0x403677C: ???
==642== by 0x46BEEFA: start_thread (pthread_create.c:309)
==642== by 0x41C1DFD: clone (clone.S:129)
==642== Address 0x403677c is not stack'd, malloc'd or (recently) free'd
==642==
==642==
==642== Process terminating with default action of signal 11 (SIGSEGV): dumping core
==642== Access not within mapped region at address 0x403677C
==642== at 0x403677C: ???
==642== by 0x46BEEFA: start_thread (pthread_create.c:309)
==642== by 0x41C1DFD: clone (clone.S:129)
==642== If you believe this happened as a result of a stack
==642== overflow in your program's main thread (unlikely but
==642== possible), you can try to increase the size of the
==642== main thread stack using the --main-stacksize= flag.
==642== The main thread stack size used in this run was 8388608.
==642==
==642== HEAP SUMMARY:
==642== in use at exit: 1,296 bytes in 6 blocks
==642== total heap usage: 515 allocs, 509 frees, 31,750 bytes allocated
==642==
==642== LEAK SUMMARY:
==642== definitely lost: 0 bytes in 0 blocks
==642== indirectly lost: 0 bytes in 0 blocks
==642== possibly lost: 136 bytes in 1 blocks
==642== still reachable: 1,160 bytes in 5 blocks
==642== suppressed: 0 bytes in 0 blocks
==642== Rerun with --leak-check=full to see details of leaked memory
==642==
==642== For counts of detected and suppressed errors, rerun with: -v
==642== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
Killed
但是,到目前为止,我一直很好,只是打开 lua 解释器并一个接一个地手动输入相同的指令。
另外,一个 C 程序做同样的事情,使用相同的库:
int start_mythread();
int main()
{
int ret = start_mythread();
return ret;
}
正如它应该的那样,在我的测试中从未失败过。
我试过 Lua 5.1 和 5.2,但无济于事。
编辑: 我应该指出我在单核 eeePC 运行 32 位 Debian Wheezy (Linux 3.2) 上对此进行了测试。
我刚刚在我的主机(4 核 64 位 Arch linux)上再次测试,每次启动脚本时都会出现 lua myscript.lua
段错误 那里...
从解释器提示符输入命令工作正常,以及上面的 C 程序。
我首先编写这个小库的原因是因为我正在编写一个更大的库,我首先遇到了这个问题。经过几个小时毫无结果的调试,包括一个一个地删除每个共享 structures/variables(是的,我当时很绝望),我归结为这段代码。
所以,我猜 Lua 我做错了什么,但那会是什么?我已经尽可能多地搜索了这个问题,但我发现大多数人在使用来自多个线程的 Lua API 时遇到问题(这不是我在这里尝试做的).
如果您有任何想法,我们将不胜感激。
编辑
更准确地说,我想知道在编写用于 Lua 脚本的 C 库时是否应该对线程采取额外的预防措施。 Lua 是否需要 从动态加载的库中创建的线程在 "unloads" 库时终止?
为什么Lua模块会出现Segfault?
您的 Lua 脚本在线程完成之前退出,这会导致段错误。 Lua 模块在正常解释器关闭期间使用 dlclose()
卸载,因此线程的指令从内存中删除,并在读取下一条指令时出现段错误。
有哪些选择?
任何在卸载模块之前停止线程的解决方案都可以使用。在主线程中使用 pthread_join()
将等待线程完成(您可能希望使用 pthread_cancel()
终止长 运行 线程)。在卸载模块之前在主线程中调用 pthread_exit()
也可以防止崩溃(因为它可以防止 dlclose()
),但它也会中止 [=44] 的正常 cleanup/shutdown 过程=] 解释器。
以下是一些有效的示例:
int pexit(lua_State* L) {
pthread_exit(NULL);
return 0;
}
int join(lua_State* L)
{
pthread_join(handle, NULL);
return 0;
}
static const luaL_Reg testlib[] = {
{"start_mythread", start_mythread_lua},
{"join", join},
{"exit", pexit},
{NULL, NULL}
};
void* mythread(void* args) {
int i, j, k;
printf("In the thread !\n");
for (i = 0; i < 10000; ++i) {
for (j = 0; j < 10000; ++j) {
for (k = 0; k < 10; ++k) {
pow(1, i);
}
}
}
pthread_exit(NULL);
}
现在脚本将正常退出:
require('test')
test.start_mythread()
print("launched thread")
test.join() -- or test.exit()
print("thread joined")
要自动执行此操作,您可以连接到垃圾收集器,因为模块中的所有对象都在卸载共享对象之前被释放。 (正如 greatwolf 所建议的那样)
Discussion on calling pthread_exit() from main(): There is a definite problem if main() finishes before the threads it spawned if
you don't call pthread_exit() explicitly. All of the threads it
created will terminate because main() is done and no longer exists to
support the threads. By having main() explicitly call pthread_exit()
as the last thing it does, main() will block and be kept alive to
support the threads it created until they are done.
(这句话有点误导:从 main()
返回大致等同于调用 exit()
,这将退出进程,包括所有 运行 线程。这可能正是您想要的行为,也可能不是。另一方面,在主线程中调用 pthread_exit()
将退出主线程,但保留所有其他线程 运行 直到它们自己或其他人停止杀死他们。同样,这可能是也可能不是您想要的行为。没有问题,除非您为您的用例选择了错误的选项。)
所以,看来我 做 必须确保在 Lua 卸载我的库时我的所有线程都已完成。
一个解决方案
我可以设置卸载库时调用的清理函数。
在这个函数中,我可以确保我的库启动的所有线程都已终止。如果我分离的线程仍然是 运行,那么从中调用 pthread_exit
可能很容易,但我不确定 safe/clean 是怎样的,因为它会突然中断 Lua...
无论如何,我可以通过创建一个 metatable 并将 __gc
字段设置为我的清理函数来实现此目的,然后将此 metatable 影响到我的库 table in Lua 5.2。
int cleanup(lua_State* L)
{
/*Do the cleaning*/
return 0;
}
int luaopen_test(lua_State* L)
{
//for lua 5.2
//metatable with cleanup method for the lib
luaL_newmetatable(L, "test.cleanup");
//set our cleanup method as the __gc callback
lua_pushstring(L, "__gc");
lua_pushcfunction(L, cleanup);
lua_settable(L, -3);
//open our test lib
luaL_newlib(L, testlib);
//associate it with our metatable
luaL_setmetatable(L, "test.cleanup");
return 1;
}
在 Lua 5.1 中,__gc
选项仅适用于用户数据。有几种解决方案可以让它在我的情况下工作:
- Lua shutdown/End of the program execution callback
- http://lua-users.org/wiki/LuaFaq(参见“为什么 __gc 和 __len 元方法不能在 table 上工作?”)
- Greatwolf 的解决方案是拥有一个带有所述元 table 的全局对象。
我为 Lua 编写了一个非常简单的 C 库,它由一个启动线程的函数组成,所述线程除了循环之外什么都不做:
#include "lua.h"
#include "lauxlib.h"
#include <pthread.h>
#include <stdio.h>
pthread_t handle;
void* mythread(void* args)
{
printf("In the thread !\n");
while(1);
pthread_exit(NULL);
}
int start_mythread()
{
return pthread_create(&handle, NULL, mythread, NULL);
}
int start_mythread_lua(lua_State* L)
{
lua_pushnumber(L, start_mythread());
return 1;
}
static const luaL_Reg testlib[] = {
{"start_mythread", start_mythread_lua},
{NULL, NULL}
};
int luaopen_test(lua_State* L)
{
/*
//for lua 5.2
luaL_newlib(L, testlib);
lua_setglobal(L, "test");
*/
luaL_register(L, "test", testlib);
return 1;
}
现在,如果我编写一个非常简单的 Lua 脚本,它就可以:
require("test")
test.start_mythread()
运行 带有lua myscript.lua
的脚本有时会导致段错误。以下是 GDB 对核心转储的看法:
Program terminated with signal 11, Segmentation fault.
#0 0xb778b75c in ?? ()
(gdb) thread apply all bt
Thread 2 (Thread 0xb751c940 (LWP 29078)):
#0 0xb75b3715 in _int_free () at malloc.c:4087
#1 0x08058ab9 in l_alloc ()
#2 0x080513a2 in luaM_realloc_ ()
#3 0x0805047b in sweeplist ()
#4 0x080510ef in luaC_freeall ()
#5 0x080545db in close_state ()
#6 0x0804acba in main () at lua.c:389
Thread 1 (Thread 0xb74efb40 (LWP 29080)):
#0 0xb778b75c in ?? ()
#1 0xb74f6efb in start_thread () from /lib/i386-linux-gnu/i686/cmov/libpthread.so.0
#2 0xb7629dfe in clone () at ../sysdeps/unix/sysv/linux/i386/clone.S:129
主线程的堆栈会不时发生一些变化。
似乎 start_thread 函数想要跳转到给定地址(在本例中为 b778b75c),该地址有时恰好属于无法访问的内存。
编辑
我也有一个 valgrind 输出:
==642== Memcheck, a memory error detector
==642== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==642== Using Valgrind-3.10.0 and LibVEX; rerun with -h for copyright info
==642== Command: lua5.1 go.lua
==642==
In the thread !
In the thread !
==642== Thread 2:
==642== Jump to the invalid address stated on the next line
==642== at 0x403677C: ???
==642== by 0x46BEEFA: start_thread (pthread_create.c:309)
==642== by 0x41C1DFD: clone (clone.S:129)
==642== Address 0x403677c is not stack'd, malloc'd or (recently) free'd
==642==
==642==
==642== Process terminating with default action of signal 11 (SIGSEGV): dumping core
==642== Access not within mapped region at address 0x403677C
==642== at 0x403677C: ???
==642== by 0x46BEEFA: start_thread (pthread_create.c:309)
==642== by 0x41C1DFD: clone (clone.S:129)
==642== If you believe this happened as a result of a stack
==642== overflow in your program's main thread (unlikely but
==642== possible), you can try to increase the size of the
==642== main thread stack using the --main-stacksize= flag.
==642== The main thread stack size used in this run was 8388608.
==642==
==642== HEAP SUMMARY:
==642== in use at exit: 1,296 bytes in 6 blocks
==642== total heap usage: 515 allocs, 509 frees, 31,750 bytes allocated
==642==
==642== LEAK SUMMARY:
==642== definitely lost: 0 bytes in 0 blocks
==642== indirectly lost: 0 bytes in 0 blocks
==642== possibly lost: 136 bytes in 1 blocks
==642== still reachable: 1,160 bytes in 5 blocks
==642== suppressed: 0 bytes in 0 blocks
==642== Rerun with --leak-check=full to see details of leaked memory
==642==
==642== For counts of detected and suppressed errors, rerun with: -v
==642== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
Killed
但是,到目前为止,我一直很好,只是打开 lua 解释器并一个接一个地手动输入相同的指令。
另外,一个 C 程序做同样的事情,使用相同的库:
int start_mythread();
int main()
{
int ret = start_mythread();
return ret;
}
正如它应该的那样,在我的测试中从未失败过。
我试过 Lua 5.1 和 5.2,但无济于事。
编辑: 我应该指出我在单核 eeePC 运行 32 位 Debian Wheezy (Linux 3.2) 上对此进行了测试。
我刚刚在我的主机(4 核 64 位 Arch linux)上再次测试,每次启动脚本时都会出现 lua myscript.lua
段错误 那里...
从解释器提示符输入命令工作正常,以及上面的 C 程序。
我首先编写这个小库的原因是因为我正在编写一个更大的库,我首先遇到了这个问题。经过几个小时毫无结果的调试,包括一个一个地删除每个共享 structures/variables(是的,我当时很绝望),我归结为这段代码。
所以,我猜 Lua 我做错了什么,但那会是什么?我已经尽可能多地搜索了这个问题,但我发现大多数人在使用来自多个线程的 Lua API 时遇到问题(这不是我在这里尝试做的).
如果您有任何想法,我们将不胜感激。
编辑
更准确地说,我想知道在编写用于 Lua 脚本的 C 库时是否应该对线程采取额外的预防措施。 Lua 是否需要 从动态加载的库中创建的线程在 "unloads" 库时终止?
为什么Lua模块会出现Segfault?
您的 Lua 脚本在线程完成之前退出,这会导致段错误。 Lua 模块在正常解释器关闭期间使用 dlclose()
卸载,因此线程的指令从内存中删除,并在读取下一条指令时出现段错误。
有哪些选择?
任何在卸载模块之前停止线程的解决方案都可以使用。在主线程中使用 pthread_join()
将等待线程完成(您可能希望使用 pthread_cancel()
终止长 运行 线程)。在卸载模块之前在主线程中调用 pthread_exit()
也可以防止崩溃(因为它可以防止 dlclose()
),但它也会中止 [=44] 的正常 cleanup/shutdown 过程=] 解释器。
以下是一些有效的示例:
int pexit(lua_State* L) {
pthread_exit(NULL);
return 0;
}
int join(lua_State* L)
{
pthread_join(handle, NULL);
return 0;
}
static const luaL_Reg testlib[] = {
{"start_mythread", start_mythread_lua},
{"join", join},
{"exit", pexit},
{NULL, NULL}
};
void* mythread(void* args) {
int i, j, k;
printf("In the thread !\n");
for (i = 0; i < 10000; ++i) {
for (j = 0; j < 10000; ++j) {
for (k = 0; k < 10; ++k) {
pow(1, i);
}
}
}
pthread_exit(NULL);
}
现在脚本将正常退出:
require('test')
test.start_mythread()
print("launched thread")
test.join() -- or test.exit()
print("thread joined")
要自动执行此操作,您可以连接到垃圾收集器,因为模块中的所有对象都在卸载共享对象之前被释放。 (正如 greatwolf 所建议的那样)
Discussion on calling pthread_exit() from main(): There is a definite problem if main() finishes before the threads it spawned if you don't call pthread_exit() explicitly. All of the threads it created will terminate because main() is done and no longer exists to support the threads. By having main() explicitly call pthread_exit() as the last thing it does, main() will block and be kept alive to support the threads it created until they are done.
(这句话有点误导:从 main()
返回大致等同于调用 exit()
,这将退出进程,包括所有 运行 线程。这可能正是您想要的行为,也可能不是。另一方面,在主线程中调用 pthread_exit()
将退出主线程,但保留所有其他线程 运行 直到它们自己或其他人停止杀死他们。同样,这可能是也可能不是您想要的行为。没有问题,除非您为您的用例选择了错误的选项。)
所以,看来我 做 必须确保在 Lua 卸载我的库时我的所有线程都已完成。
一个解决方案
我可以设置卸载库时调用的清理函数。
在这个函数中,我可以确保我的库启动的所有线程都已终止。如果我分离的线程仍然是 运行,那么从中调用 pthread_exit
可能很容易,但我不确定 safe/clean 是怎样的,因为它会突然中断 Lua...
无论如何,我可以通过创建一个 metatable 并将 __gc
字段设置为我的清理函数来实现此目的,然后将此 metatable 影响到我的库 table in Lua 5.2。
int cleanup(lua_State* L)
{
/*Do the cleaning*/
return 0;
}
int luaopen_test(lua_State* L)
{
//for lua 5.2
//metatable with cleanup method for the lib
luaL_newmetatable(L, "test.cleanup");
//set our cleanup method as the __gc callback
lua_pushstring(L, "__gc");
lua_pushcfunction(L, cleanup);
lua_settable(L, -3);
//open our test lib
luaL_newlib(L, testlib);
//associate it with our metatable
luaL_setmetatable(L, "test.cleanup");
return 1;
}
在 Lua 5.1 中,__gc
选项仅适用于用户数据。有几种解决方案可以让它在我的情况下工作:
- Lua shutdown/End of the program execution callback
- http://lua-users.org/wiki/LuaFaq(参见“为什么 __gc 和 __len 元方法不能在 table 上工作?”)
- Greatwolf 的解决方案是拥有一个带有所述元 table 的全局对象。