`

对可抢占和非抢占脚本引擎的思考

 
阅读更多

 

一、脚本语言的可抢占能力

根据脚本引擎核心的抢占能力,脚本引擎大概可分三类:

1. 第一类脚本,它的语句是细粒度的,而且如果它没有完成所有命令,就不能被宿主抢占(脚本不能被打断)。

例如Python,它提供的PyRun_SimpleString是不可抢占(除非它自己退出,或在脚本运行于一个后台线程内)

    2. 第二类脚本,它实际上是宏命令集合(相当于描述或配置文件)。它的执行过程可以被宿主打断。

    例如XML,它的执行(实际上是解析)速度很快。脚本既可以一次性地向宿主传递数据就马上退出,也可以以单步的形式在脚本解析和宿主执行之间轮流切换。

    3. 第三类脚本,它们的虚拟机核心部分是可抢占的,即脚本自身可以暂停或挂起自己,通过退出虚拟机把控制权交还宿主。等待宿主完成某些事情后重新恢复脚本的执行。

    例如Lua,允许使用一种叫“协程”的机制,并且提供一个API叫lua_resume,使Lua和宿主的执行流能有尽可能多的机会交错切换(详细可以参考这个链接(日文):http://marupeke296.com/LUA_No3_Coroutine.html)。由于可抢占的优势,Lua可以轻松处理事件驱动的系统。只要它有机会挂起自己,就能避免“程序无响应”的情况(前提是脚本执行的时间粒度足够小)。

 

二、如何让非抢占脚本引擎与消息循环共存

如上所述,脚本的可抢占能力与实时事件的处理能力是相关联的。如果脚本不需要花太多的时间(不处理鼠标事件),那么能否被抢占是无需考虑的。但万一脚本需要花很多时间(需要处理鼠标事件),而且脚本不可抢占,那么程序就可能困死在脚本中,没有时间处理窗口事件。

问题是:如果选择的脚本语言是单线程(同一时间只能执行一段脚本)且不可抢占(阻塞的),如何让它和实时系统(窗口系统)共存。

我认为解决方法有以下几种:

1. 多线程

由于脚本的执行是同步的,与异步的窗口事件处理不兼容,可以显式地创建一个后台线程,让脚本的执行独立于窗口系统,通过线程间的共享内存进行通信。需要考虑多线程的数据竞争问题。

2. 在脚本内处理窗口系统的消息循环

由于Windows的窗口处理实际上是多线程的,可以让主线程的消息循环在脚本内执行,使窗口事件的处理不被阻塞。虽然这种方法没有显式创建线程,但原理实际上和方法1相同。

3. 与可抢占的脚本语言混合使用

使用多于一种脚本引擎来操纵程序。需要考虑不同脚本状态机之间的数据共享问题。

4. 把一个脚本分拆成多个脚本

让窗口事件处理(如鼠标事件)的逻辑单独放在一个脚本文件。不过如果脚本状态机不是全局的,还需要留意状态数据的共享问题。

我觉得第2种办法是最优雅的,因为它不需要太复杂的机制。不过这么做的话,脚本既要处理逻辑,还要处理底层。

 

三、简单地在Python脚本中嵌入Win32消息循环

我尝试用第2种解决方法在Win32窗口程序内嵌入Python 2.2.2脚本(模仿一个日本游戏引擎KAVG的做法)。

大概写法如下(注意,这里忽略脚本的错误信息输出和窗口输入处理,而且用PyRun_SimpleString不太好,仅供参考)

 

脚本引擎部分:

 

 

#include <Python.h>
#include <windows.h>
#include "script.h"
#include "mainframe.h"

static PyObject *trace(PyObject *self, PyObject *args)
{
    char* input;
    if (!PyArg_ParseTuple(args, "s", &input))
	{
		return NULL;
	}
	OutputDebugString(input);
	OutputDebugString("\n");
	return PyInt_FromLong(0);
}

static PyObject *foo(PyObject *self, PyObject *args)
{
	return PyInt_FromLong(42L);
}

static PyObject *peekMsg(PyObject *self, PyObject *args)
{
	return PyInt_FromLong(MainFrameMainLoop());
}

void PyInit_SAVG(void)
{
	PyObject *m;
	static PyMethodDef SAVG_methods[] = {
		//{"foo", (PyCFunction)foo, METH_NOARGS, "Return the meaning of everything."},
		{"trace", (PyCFunction)trace, METH_VARARGS, "Output debug info for debugging."},
		{"peekMsg", (PyCFunction)peekMsg, METH_NOARGS, "Window message loop"},
		{NULL, NULL}
	};
	PyImport_AddModule("SAVG");
	m = Py_InitModule("SAVG", SAVG_methods);
	PyModule_AddStringConstant(m, "SAVG_VERSION", SAVG_VERSION);
}

static int script_init(void)
{
	int ret;
	ret = PyRun_SimpleString(
		"import SAVG\n"
		"SAVG.trace(\"Script environment is initializing...\")\n"
		"SAVG.trace(\"SAVG_VERSION is %s\" % SAVG.SAVG_VERSION)\n"
	);
	if(ret == -1)
	{
		OutputDebugString("error on script_init\n");
		return 0;
	}
	return 1;
}

static void script_main(void)
{
	int ret;
	ret = PyRun_SimpleString(
		"while 1:\n"
		"	if SAVG.peekMsg():\n"
		"		break\n"
	);
	if(ret == -1)
	{
		OutputDebugString("error on script_main\n");
	}
}

//NOTE:This function is in WinMain (Main Thread)
int runScript(void)
{
	Py_SetProgramName("SimpleScriptSystem");
	Py_Initialize();
	PyInit_SAVG();
	if(script_init())
	{
		script_main();
	}
	Py_Finalize();
	return 0;
}

 

 

主窗口(部分代码):

 

 

int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
	UNREFERENCED_PARAMETER(hPrevInstance);
	UNREFERENCED_PARAMETER(lpCmdLine);
	MyRegisterClass(hInstance);
	if(!InitInstance(hInstance, nCmdShow))
	{
		return FALSE;
	}
	//TODO: Using script engine to dispatch message,
	//or the main window will have no response!!!
	//As follow:
	//	while(1)
	//	{
	//		if(MainFrameMainLoop())
	//			break;
	//	}
	runScript();
	//FIXME:
	//the return value of program should be : 
	//(int) msg.wParam;
	return 0; 
}

int MainFrameMainLoop(void) 
{
	MSG msg;
	if(!GetMessage(&msg, 0, 0, 0))
	{
		return 1;
	}
	if(!TranslateAccelerator(msg.hwnd, NULL, &msg)) 
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return 0;
}

 

 

原本执行GetMessage的循环被Python脚本接管了,所以即使script_main没有执行完,窗口系统也不会失去响应。

此时Python脚本不能直接处理输入事件,只能在peekMsg之后轮询输入缓冲(上面的代码没有实现此功能)。

 

四、总结

用类似Python的不可抢占脚本语言处理实时事件是可以的,关键是让脚本内的微观操作尽快完成,但不需要让整个脚本尽快完成。在山穷水尽的时候,还可以依靠操作系统的多线程能力。

对于win32消息循环,Python脚本可以通过C扩展来避免使用协程。

 

五、参考资料

1. Lua組み込み編:その3 コルーチンで状態遷移をLuaで制御

http://marupeke296.com/LUA_No3_Coroutine.html

2. Lua下实现抢占式多线程

http://blog.codingnow.com/2011/08/lua_52_multithreaded.html

3. 游戏引擎脚本系统(二)

http://www.blogjava.net/tianlinux/archive/2007/06/01/121434.html

 

 
分享到:
评论

相关推荐

    华西抢号Python脚本

    10. **安全与道德问题**:使用抢号脚本需注意遵守华西医院的挂号规定,尊重网络公平原则,不进行恶意抢占或破坏系统的行为。同时,由于涉及到个人敏感信息,脚本应确保数据的安全传输和存储。 了解以上知识点后,你...

    scramble4gpu:抢占显卡-*-

    scramble4gpuscramble4gpu是用来在抢占显卡的脚本,主要是在实验室显卡资源紧张且自己亟需使用显卡的情况下,使用该脚本自动抢占一个或多个显卡。建议将scramble4gpu.py更改为train.py;防止被打...依赖numpytorch ...

    DataCastle竞赛“抢占C位,燃情世界杯”,秒杀脚本.zip

    在“DataCastle竞赛‘抢占C位,燃情世界杯’,秒杀脚本.zip”这个压缩包中,我们主要关注的是“世界杯程序”的实现,特别是其中的“秒杀脚本”。秒杀系统通常用于处理高并发场景,比如在世界杯期间,可能会有大量...

    ros3.x的HTB+PCQ脚本

    RouterOS是由MikroTik公司开发的一款强大的路由器操作系统,它提供了丰富的配置选项和脚本能力,适用于企业级网络环境。 HTB(Hierarchical Token Bucket)是一种流量整形工具,它基于Token Bucket算法,将不同...

    华为MSTP+VRRP脚本.rar

    在本压缩包“华为MSTP+VRRP脚本.rar”中,包含了相关的实验配置和脚本,这将帮助我们深入了解这两种协议的工作原理及配置方法。 **MSTP(多生成树协议)** 是STP(生成树协议)的一种增强版本,旨在解决传统的STP在...

    最新卡BUG的脚本,要的速度,不然缺货

    3. **上A房和B房**:在射击游戏中,A房和B房可能指的是地图上的两个关键区域,如控制点或目标点,通过脚本可以实现快速、异常地到达这些区域,可能有利于战术布置或抢占先机。 4. **卡矿场**:这可能是游戏中的一个...

    Melang:单线程抢占式调度协程的脚本语言

    Melang是单线程中抢先调度协程的脚本语言。 仅在UNIX / Linux上受支持。 Melang已经支持MySQL 8.0,但是最新MySQL C客户端库是不稳定的。 因此,如果尝试连接无法访问的地址,即使程序可能不会崩溃,也会发生缓冲区...

    avr-simple-scheduler:一个非常简单的,非抢占式的任务计划程序

    - Makefile:用于编译和构建项目的脚本。 使用avr-simple-scheduler时,开发者需要注意以下几点: - 任务的执行时间:避免编写可能导致长时间阻塞的操作,如长时间的I/O操作或计算密集型任务。 - 资源管理:由于AVR...

    graylog-sidecar部署脚本

    该脚本是由shell语言编写完成,主要用于实现一键部署graylog-sidecar组件,其中包含...下载该脚本后可根据自身环境的服务器大小,来动态调整cgroup资源限制,使得该组件能以更优的方式运行。不会发生抢占系统资源现象。

    VoltyChannel_Stop - MetaTrader 5脚本.zip

    《MetaTrader 5脚本——VoltyChannel_Stop详解与应用》 在金融市场交易的世界里,技术分析工具扮演着至关重要的角色。...掌握并熟练运用此脚本,无疑能够提升交易效率,助力交易者在复杂多变的市场中抢占先机。

    Swoole4.4协程抢占式调度器详解

    其次,使用PHP的ticks机制也存在限制,即ticks指令只能在当前脚本范围内有效,而不能作用于require或include引入的脚本,这使得抢占式调度器的实现变得复杂。 Swoole团队考虑了多种方法来实现抢占式调度,最终选择...

    Price_Alert - MetaTrader 4脚本.zip

    2. **市场数据获取**:MT4平台提供了内置函数来获取实时的市场报价,如`SymbolInfoDouble(Symbol(), SYMBOL_BID)`和`SymbolInfoDouble(Symbol(), SYMBOL_ASK)`分别获取当前货币对的买价和卖价。 3. **价格比较与...

    京东秒杀脚本窗口可视化 类似于12306的抢票软件,可以监控下架和预约的商品,监控库存 ,自动下单通知.zip

    这款脚本的主要特点在于其可视化界面,使得操作更加直观易懂,同时也提供了强大的商品监控和自动化下单通知功能。 首先,我们要理解什么是“秒杀”。秒杀是电商平台为了促销而推出的一种限时限量销售模式,商品在短...

    Intraday_Intensity_Open_Form - MetaTrader 5脚本.zip

    总之,"Intraday_Intensity_Open_Form"是一款旨在提升日内交易效率的MT5脚本,通过对市场强度和开盘形态的综合分析,为交易者提供决策支持。通过深入理解和有效利用这一工具,交易者可以在瞬息万变的市场中抢占先机...

    使命召唤10:幽灵(CallofDutyGhosts)正版多人模式CE多项修改脚本.zip

    另外,使用非官方的修改脚本也存在安全风险,可能导致电脑被植入恶意软件。 对于喜欢探索游戏可能性的玩家来说,理解CE脚本的工作原理和如何编写,也是一种技术学习的过程。CE本身是一个强大的内存调试工具,通过...

    内核安全软件仿XT,开源,支持LUA脚本

    DPCs用于在高优先级的后台任务中执行非抢占式操作,而IOTIMER则是处理I/O事件的时间触发器。在内核安全软件仿XT中,对这些机制的监控能够防止恶意程序滥用它们进行恶意活动,如持久化、隐藏或者干扰系统服务。 SSDT...

    毕业设计,基于SpringBoot+Vue+MySql开发的前后端分离的光电论坛网站,内含Java完整源代码,数据库脚本

    在多维光通信/光处理和非线性/纳米光子器件/新型存储器件/计算与存储高效融合/智能存储等基础前沿研究中取得原创性成果,掌握自主知识产权,突破封锁,抢占先机实现弯道超越。 在能量光电子领域,面向我国能源结构...

    阿里云 专有云企业版 V3.6.2 智能数据引擎Dataphin 技术白皮书 - 20181228.pdf

    1.2 **一体化开发**:提供统一的开发平台,支持SQL脚本编写、任务调度、数据质量监控等,确保数据处理过程的规范性和一致性。 1.3 **自动化运维**:内置完善的运维监控体系,实时跟踪数据流程,自动预警异常,降低...

    Node.js高效分布式实时应用研究.pdf

    Node.js是一个基于Chrome V8引擎的JavaScript运行环境,它以其事件驱动、非阻塞I/O模型而被广泛应用于构建高效、可扩展的网络应用,尤其是在实时应用领域。本文主要探讨了如何利用Node.js平台构建分布式实时应用,并...

    Cellular:抢占式的基于lua5.1的类Actor模型的简单实现,沙箱之间共享全局环境

    4、因为各沙箱对全局环境有读权限,各沙箱可以共享全局环境,避免lua基础库和公共脚本的重复加载。5、在调度的过程中通过lua hook的方式实现任务的抢占式调度。Build && Testcd Cellular && make./testcase

Global site tag (gtag.js) - Google Analytics