Lua与多线程C程序交互注意事项

背景

最近工作上用到Lua脚本和C/C++程序交互操作,其中某个功能需要C程序异步通知Lua脚本工作状态,然后Lua脚本会在检测点检测到该状态并做相应处理。然后在测试时发现异常退出的bug,于是觉得有必要研究下此方面内容,故写此文。

由于Lua脚本调用的检测点函数也是C程序写的,所以相当于在Lua提供的API下进行C程序的多线程交互,所以直接省去了lua脚本这一层。

实验过程与推理

实验环境:

Fedora 14(Linux 2.6.35.14.i686), gcc 4.5.1, lua5.1.4

程序命名:

tlua.c

编译命令:

gcc tlua.c -g -llua -lpthread -o tlua

一、首先写一个不加锁的多线程读写LUA内置注册表中元素的程序

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER;
const char * lua_key = "test";
lua_State *L;
void* test_thread(void *para)
{
	while (1)
	{
		lua_pushstring(L, lua_key);
		lua_gettable(L, LUA_REGISTRYINDEX);
		printf("Get %d\n", lua_tointeger(L, -1));
		lua_pop(L, 1);
	}
	return NULL;
}
int main()
{
	L = lua_open();
	luaL_openlibs(L);
	pthread_t thread;
	if (pthread_create(&thread, NULL, test_thread, NULL))
	{
		puts("Create thread error");
		exit(-1);
	}
	int i =1;
	while (1)
	{
		printf("Set %d\n", i);
		lua_pushstring(L, lua_key);
		lua_pushinteger(L, i++);
		lua_settable(L, LUA_REGISTRYINDEX);
	}
	return 0;
}

运行一段时间后会Segmentation fault 或者其他错误。主要问题分析为:

1、栈不平衡:因为是多线程的,所以main和test_thread里面的while循环可能交错运行,例如main循环push入一个值后,线程调度到test_thread里面的pop,这导致lua栈失去平衡,最终崩溃。

2、lua_State内部状态不一致:这出现在lua函数内部,由于lua_pushstring/lua_settable这样的操作会更改lua的内部状态,在这些状态没有更该完毕之前就去进行另外的更改或者读取操作就去运行,会导致错误。

于是我们来加锁试试。

二、加锁版本的多线程读写,在实验一的基础上修改代码如下:

//In test_thread while
	while (1)
	{
		pthread_mutex_lock(&g_mutex);
		lua_pushstring(L, lua_key);
		lua_gettable(L, LUA_REGISTRYINDEX);
		printf("Get %d\n", lua_tointeger(L, -1));
		lua_pop(L, 1);
		pthread_mutex_unlock(&g_mutex);
	}
//In main while
	while (1)
	{
		pthread_mutex_lock(&g_mutex);
		printf("Set %d\n", i);
		lua_pushstring(L, lua_key);
		lua_pushinteger(L, i++);
		lua_settable(L, LUA_REGISTRYINDEX);
		pthread_mutex_unlock(&g_mutex);
	}

这样就解决了实验一中的两个问题,然后编译长时间运行之没有发生错误。然后我又测试了读写不同的table,读同一个table,读不同的table,不加锁都会有问题。然而唯一遗憾的是,由于锁的颗粒度,不能测试到底是两个问题中的哪个问题导致的错误,这个只能通过读lua源代码来判断。

总结

上面写了那么多代码,而且挂上了lua的名号,但无论是lua栈平衡还是lua内部状态在宏观上来看都仍然是传统的多线程中的数据一致性问题。只不过中间间隔了一层Lua脚本,使得问题看起来复杂化,通过问题的抽象从而找到根本所在。于是最终结论如下:

如果要多线程处理lua的话,请为每个lua操作都加上锁,无论读写。

This entry was posted in Lua, 程序设计 and tagged , , . Bookmark the permalink.

17 Responses to Lua与多线程C程序交互注意事项

  1. fleuria says:

    ruby和python是内部一个大解释器锁,只要是操作解释器相关的数据结构,就统统交给这个大锁锁着。这一来三方库的开发者就可以不用管任何的同步问题了

    • davelv says:

      恩,这是给开发者带来了很多便利,但缺点就是交互性能的下降。lua作为异构交互首选脚本就是因为它极高的速度,看到有人测试的数据,大概在百万级上。
      究竟要用哪种还得看主程序对性能的需求。
      我写点日常使用的应用程序自然还是首选python/shell,对速度没要求,关键是方便。

      • fleuria says:

        是的, 这该是ruby和python并发起来比较困难的一个原因。lua的内部接口做的很漂亮,如果并发,协程也是个很好的做法,在性能与复杂度之间有一个很好的折中。

  2. snail says:

    虽不明,但觉厉

  3. S says:

    啊。除了汉字其他的看不懂。
    崇拜。

  4. 大D says:

    多线程。。。看着好懵。。各种不懂。。YM Dave酱

  5. kshaoye says:

    没接触过LUA

  6. xushine says:

    遥想我那本魔兽世界的插件编程的书还木有翻译完成 D大已然熟练的用lua了 表示要向D大学习

  7. snail says:

    为什么KC的博客里关于你的介绍是“大长茎童鞋”?? 求辟谣….

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注