摘要: PHP从诞生以来就受到广大编程爱好者的喜欢,成为中小站长的好帮手,并培养了大量的PHP编程人员,但是随着PHP的应用越发广泛,很多时候已不限于从事中小网站的应用,一些大型PHP项目也屡见不鲜。 当我们选择PHP开发大型项目时,就不得不考虑开发效率、开发规范、后期维护等问题,这时大家往往会选择一款人们所认可的开发框架,目前所流行的Zend Framework、Yii、Symfony、CodeIgniter、CakePHP等都声称有着开发大型应用的能力。 新框架层出不穷,但当我们真正应用这些框架去实现产品又总是会有各种不同的问题产生。阅读全文
posted @ 2011-10-14 23:42 lajabs 阅读(1819) 评论(7) 编辑

v8是google开发的一个js引擎,其性能非常出色,包括Chrome在内的开源产品都在使用它,同时我们知道Chrome采用的是多进程模式,本文主要是例举一个基于v8的多进程JS编程API。

#include <vector>
#include
"v8.h"

//读取js文件,返回js文件源码
v8::Handle<v8::String> ReadFile(const char* name)
{
FILE
* file = fopen(name, "rb");
if (file == NULL) return v8::Handle<v8::String>();
fseek(file,
0, SEEK_END);
int size = ftell(file);
rewind(file);
char* chars = new char[size + 1];
chars[size]
= '\0';
for (int i = 0; i < size;)
{
int read = fread(&chars[i], 1, size - i, file);
i
+= read;
}
fclose(file);
v8::Handle
<v8::String> result = v8::String::New(chars, size);
delete[] chars;
return result;
}

//执行js源代码,其中有编译和运行两个小步骤,并直接打印结果
void ExecuteString(const v8::Handle<v8::String> &source)
{
v8::HandleScope handle_scope;
v8::Handle
<v8::Script> script = v8::Script::Compile(source);

v8::Handle
<v8::Value> result = script->Run();
v8::String::Utf8Value str(result);
printf(
"%s\n", *str);
}


//////////////////////////////////////////


//线程模块,分为初始化和运行两个步骤
class Th : public v8::internal::Thread
{
public:
Th(
const char * js_func_name) : Thread("nameless")
{
strcpy(_fname,js_func_name);
}

virtual void Run()
{
Execute4Thread();
}
private:
void Execute4Thread()
{
v8::Isolate
* isolate = v8::Isolate::New();
v8::Isolate::Scope iscope(isolate);
v8::HandleScope scope;

v8::Persistent
<v8::Context> context = v8::Context::New();
v8::Context::Scope cscope(context);
run_proc();
context.Dispose();
}

void run_proc()
{
ExecuteString(ReadFile(_fname));
}

char _fname[20];
};

/////////////////////////////////////////


// 这个用于从js的参数中获取参数所属的c++对象,并返回对象指针
template<class T>
T
* get_cppobj_ptr(v8::Handle<v8::Object> obj)
{
v8::Handle
<v8::External> field = v8::Handle<v8::External>::Cast(obj->GetInternalField(0)) ;
void* raw_obj_ptr = field->Value() ;
return static_cast<T*>(raw_obj_ptr);
};


//暴露给js的c++对象,用于创建和运行线程
class Worker
{
public:
static v8::Handle<v8::Value> Start(const v8::Arguments & args)
{
Worker
* worker_ptr = get_cppobj_ptr<Worker>(args.Holder()) ;
worker_ptr
->StartWorker(args);
}

static v8::Handle<v8::Value> Join(const v8::Arguments & args)
{
Worker
* worker_ptr = get_cppobj_ptr<Worker>(args.Holder()) ;
worker_ptr
->JoinWorker(args);
}

inline
void StartWorker(const v8::Arguments & args)
{
for (int i = 0; i < args.Length(); i++)
{
v8::String::Utf8Value str(args[i]);
Th
*th = new Th(*str);
th
->Start();
ths_.push_back(th);
}
}

inline
void JoinWorker(const v8::Arguments & args)
{
std::vector
<Th*>::iterator it = ths_.begin();
for (; it!=ths_.end(); ++it)
{
(
*it)->Join();
}
}
private:
std::vector
<Th*> ths_;
};


//注册相关的c++对象到js环境中
void creat_obj(v8::Persistent<v8::Context> &exec_context)
{
v8::Handle
<v8::ObjectTemplate> foo_templ ;
v8::Handle
<v8::Object> foo_class_obj ;
v8::Handle
<v8::External> foo_class_ptr ;

Worker
* g_foo_ptr = new Worker;
foo_templ
= v8::ObjectTemplate::New();
foo_templ
->SetInternalFieldCount(1);
foo_class_obj
= foo_templ->NewInstance();

foo_class_ptr
= v8::External::New(static_cast<Worker *>(g_foo_ptr)) ;
foo_class_obj
->SetInternalField(0, foo_class_ptr) ;

foo_class_obj
->Set(v8::String::New("Start"),
v8::FunctionTemplate::New(Worker::Start)
->GetFunction()) ;

foo_class_obj
->Set(v8::String::New("Join"),
v8::FunctionTemplate::New(Worker::Join)
->GetFunction()) ;

exec_context
->Global()->Set(v8::String::New("Worker"),
foo_class_obj,
(v8::PropertyAttribute)(v8::ReadOnly)) ;
}



int main()
{
v8::HandleScope handle_scope;
v8::Persistent
<v8::Context> exec_context = v8::Context::New();
v8::Context::Scope context_scope(exec_context);
creat_obj(exec_context);

ExecuteString(ReadFile(
"test.js"));
return 0;
}

  

假设我们有三个js脚本文件:

// test.js 
Worker.Start('test1.js');
Worker.Start(
'test2.js');
Worker.Join();
'ok';


// test1.js
var str = 'test1';
for(i=0;i<=10000;i++)
str;



// test2.js
var str = 'test2';
str;

  

这边使用的是linux环境下,v8默认使用pthread库,通过静态编译以上的c++程序,

[root@hx test]# g++ -o test test.cpp  libv8.a -I/var/www/cpp/v8/src -lpthread -fno-rtti -fno-exceptions

[root@hx test]# ./test

test2

test1

ok

我们可以看到由于test1的运行时间比较长,故而后输出结果。

posted @ 2011-08-16 20:30 lajabs 阅读(880) 评论(0) 编辑

v8引擎为Google开发的JS语言解释器有着高效易用等特点,通常它执行一个Js脚本需要经过编译和运行步骤,

由于我们的脚本程序很可能不正确,随时造成过程环节的异常,我们来解决一个异常以及错误的捕捉和处理过程,如下脚本:

test.js:

var str = 'Hello';
str
= str + ', World!';
alert(str);

  

使用以下经典的V8 C++示例代码来执行脚本:

#include <v8.h>
using namespace v8;

//提取js文件
v8::Handle<v8::String> ReadFile(const char* name)
{
FILE
* file = fopen(name, "rb");
if (file == NULL) return v8::Handle<v8::String>();
fseek(file,
0, SEEK_END);
int size = ftell(file);
rewind(file);
char* chars = new char[size + 1];
chars[size]
= '\0';
for (int i = 0; i < size;)
{
int read = fread(&chars[i], 1, size - i, file);
i
+= read;
}
fclose(file);
v8::Handle
<v8::String> result = v8::String::New(chars, size);
delete[] chars;
return result;
}

int main()
{
HandleScope handle_scope;
Handle
<Context> context = Context::New();
Context::Scope context_scope(context);

const char * fname = "test.js";
Handle
<String> source = ReadFile(fname);
//ExecuteString(source, String::New(fname), true, true);

Handle
<Script> script = Script::Compile(source);
Handle
<Value> result = script->Run();
String::AsciiValue ascii(result);
printf(
"%s\n", *ascii);
return 0;
}

 

然后编译程序 

# g++ -o test test.cpp -lv8 

# ./test

<unknown>:58: Uncaught ReferenceError: alert is not defined

Segmentation fault

在执行过程遇到JS语法错误时,V8很干脆地中上了进程,提醒段错误。

在这我们正常应用环境是无法使用的,需要有一种异常处理机制来收集并处理错误。

这就是v8::TryCatch ,在编译前声明,在之后的编译和执行中只要有错误的发生均可捕获。

查找了相关的V8源代码,改进后的代码实现如下(函数体),

其中source为源文件内容,name用来标记当前脚本名称(文件名),

print_result表示是否打印脚本执行结果,report_exceptions表示是否报告异常(错误):

 

bool ExecuteString(v8::Handle<v8::String> source,
v8::Handle
<v8::Value> name,
bool print_result,
bool report_exceptions)
{
v8::HandleScope handle_scope;
v8::TryCatch try_catch;
//这里设置异常机制
v8::Handle<v8::Script> script = v8::Script::Compile(source, name);
if (script.IsEmpty()) {
// Print errors that happened during compilation.
if (report_exceptions)
ReportException(
&try_catch);
return false;
}
else {
v8::Handle
<v8::Value> result = script->Run();
if (result.IsEmpty()) {
assert(try_catch.HasCaught());
// Print errors that happened during execution.
if (report_exceptions)
ReportException(
&try_catch);
return false;
}
else {
assert(
!try_catch.HasCaught());
if (print_result && !result->IsUndefined()) {
// If all went well and the result wasn't undefined then print
// the returned value.
v8::String::Utf8Value str(result);
const char* cstr = ToCString(str);
printf(
"%s\n", cstr);
}
return true;
}
}
}

 

相关的异常处理函数:

 

const char* ToCString(const v8::String::Utf8Value& value) {
return *value ? *value : "<string conversion failed>";
}


void ReportException(v8::TryCatch* try_catch) {
v8::HandleScope handle_scope;
v8::String::Utf8Value exception(try_catch
->Exception());
const char* exception_string = ToCString(exception);
v8::Handle
<v8::Message> message = try_catch->Message();
if (message.IsEmpty()) {
// V8 didn't provide any extra information about this error; just
// print the exception.
printf("%s\n", exception_string);
}
else {
// Print (filename):(line number): (message).
v8::String::Utf8Value filename(message->GetScriptResourceName());
const char* filename_string = ToCString(filename);
int linenum = message->GetLineNumber();
printf(
"%s:%i: %s\n", filename_string, linenum, exception_string);
// Print line of source code.
v8::String::Utf8Value sourceline(message->GetSourceLine());
const char* sourceline_string = ToCString(sourceline);
printf(
"%s\n", sourceline_string);
// Print wavy underline (GetUnderline is deprecated).
int start = message->GetStartColumn();
for (int i = 0; i < start; i++) {
printf(
" ");
}
int end = message->GetEndColumn();
for (int i = start; i < end; i++) {
printf(
"^");
}
printf(
"\n");
v8::String::Utf8Value stack_trace(try_catch
->StackTrace());
if (stack_trace.length() > 0) {
const char* stack_trace_string = ToCString(stack_trace);
printf(
"%s\n", stack_trace_string);
}
}
}

 

最后修改main入口:
int main(int argc, char* argv[])
{
HandleScope handle_scope;

Handle
<Context> context = Context::New();
Context::Scope context_scope(context);

const char * fname = "test.js";
Handle
<String> source = ReadFile(fname);
ExecuteString(source, String::New(fname),
true, true);

return 0;
}

  

重新编译后运行结果为:

# ./test

test.js:3: ReferenceError: alert is not defined

alert(str);

^

ReferenceError: alert is not defined

    at test.js:3:1

已经可以正常报告错误。

posted @ 2011-08-10 16:06 lajabs 阅读(347) 评论(1) 编辑

网络服务器通常要应对一些意外情况,如空连接行为,指在遇到客户端连接后不进行任何操作,并很可能在大量空连接情况下导致服务器资源耗尽而无法工作。

以下代码主要工作在连接后首次接收客户端消息的环节添加一个timer,并在指定时间后检测是否已接收到消息(验证消息环节此处略去),如果没有收到消息则可认为这是一个非正常连接,并马上断开。timer使用boost::asio::deadline_timer,相对比较简单,详情如下:


#include <boost/asio.hpp>
#include
<boost/bind.hpp>
#include
<boost/date_time/posix_time/posix_time_types.hpp>
#include
<boost/shared_ptr.hpp>
#include
<iostream>

using namespace boost::asio;
using boost::asio::ip::tcp;

class Session : public boost::enable_shared_from_this<Session>
{
public:
Session(boost::asio::io_service
& io_service):
timer_(io_service)
,socket_(io_service)
,is_read_(
false)
{
Start();
}

void Start()
{
socket_.async_read_some
(
boost::asio::buffer(buff,
1024),
bind
(
&Session::HandleRead,
shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred
)
);
//当客户端连接上5秒钟以后检测是否进行了通信
timer_.expires_from_now(boost::posix_time::seconds(5));
timer_.async_wait(boost::bind(
&Session::time_out, shared_from_this()));
}

void HandleRead(const boost::system::error_code& error, size_t len)
{
if (!error)
{
is_read_
= true;
cout
<< buff << endl;
socket_.async_read_some
(
boost::asio::buffer(buff,
1024),
bind
(
&Session::HandleRead,
shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred
)
);
}
}
private:
void time_out()
{
//判断没有从客户端读取到消息的情况下断开连接
if(!is_read_) socket_.close();
}

private:
deadline_timer timer_;
tcp::socket socket_;
char buff[1024];
bool is_read_;
};

  

posted @ 2011-07-26 12:07 lajabs 阅读(366) 评论(0) 编辑


最近发生一个异常中再次验证了“what(): boost::lock_error”属于对象成员“boost::mutex m_mtx” 被销毁后的调用引用的,有时也会伴随Segment fault的发生。

第一反应自然是悬空指针的调用问题,指针指向了一个被销毁的对象,并在某个时间调用这个不存在的对象。但在前几次排查中没有发现这个问题,因为我的对象生成后一直使用shared_ptr,于是把问题扩展化,反复调试和查看boost源代码,甚至一度怀疑库的问题?

事实再一次证明了一个观点,C++的库是经过严格考验,任何问题的发生只可能是人的问题,而不是库。案发现场如下:

    async_write
(
m_socket,
boost::asio::buffer(msg.c_str(), msg.size()),
bind
(
&Games::HandSend, this,
boost::asio::placeholders::error
)
);

  

上面这个例子是一个很常见的boost.asio的异步执行代码,如果没认真看很容易忽略了bind参数中的“this”参数,这正是当前对象的指针!异步执行过程中另一个线程会持有这个"this"指针并在未知的时间里回调HandSend方法,而在持有这个指针的时间内,这个线程并不知道当前对象有可能已经被销毁,所以造成很低级的悬空指针案件。

正确的做法应该是:

    async_write
(
m_socket,
boost::asio::buffer(msg.c_str(), msg.size()),
bind
(
&Games::HandSend, shared_from_this(),
boost::asio::placeholders::error
)
);

  

在asio中使用了shared_from_this智能指针,而且这个在asio中是必须使用的,否则几乎|肯定会发生问题。这又再次让我领略到在C++中使用裸指针的危害,只不过以往没有对"this"这个指向自身的指针有过足够的重视。

posted @ 2011-07-26 11:24 lajabs 阅读(315) 评论(0) 编辑

假设一种环境,我们要对服务热拔插一个动态库(.so文件),所要考虑的是多线程环境的兼容,不会因为动态库替换后造成栈损毁而崩溃。
这边想到的方法就是封装一个dlopen过程作为对象实例加载(见load_so.h),当发出更新动态库时重新dlopen过程,替换原先的实例,注意这个替换过程必须是温和的、无逢的,这边我们使用智能指针实现。

具体更新的实现通过一个单例(见do_sth.h),调用Reload重新加载动态库。

我们构造一个极简单的动态库测试:
make_so.h

#include "say.h"
extern "C"
{
void Enter(const std::string &str)
{
Say::instance().Sth(str);
//在这里动态库又过来调用了主程序的单件
}
}

  

say.h 打印消息,这边只是声明一

#include "singleton.h"
class Say : public Singleton <Say>
{
public:
void Sth(const std::string &str);
};

  


通过编译生test.so:
g++ make_so.cpp -fPIC -shared -pthread -rdynamic -lboost_thread -lboost_system -o test.so -L[boost库目录]

主程序 test.cpp ,用来测试这个动态库test.so



#include <boost/thread.hpp>
#include
"load_so.h"
#include
"say.h"

class DoSth : public Singleton
{
public:
DoSth():m_so(
new LoadSo("./test.so"))
{
}

DynamicSo_ptr Get()
{
boost::mutex::scoped_lock
lock(m_mtx);
return m_so;
}

void Reload()
{
boost::mutex::scoped_lock
lock(m_mtx);
m_so.reset(
new LoadSo("./test.so"));
}
private:
DynamicSo_ptr m_so;
boost::mutex m_mtx;
};



/// Say
void Say::Sth(const std::string &str)
{
std::cout
<< str << std::endl;
}




////////////////////////// 测试代码 //////////////////////////////////////

//更新动态库
void test()
{
for(int i=0;i<100;++i)
{
sleep(
1);
DoSth::instance().Reload();
}
}

//运行动态库
void test2()
{
for(int i=0;irun("12");
}
}

int main()
{
std::cout
<< "run\n";

boost::thread thread1(
&test2);
boost::thread thread2(
&test);

thread1.join();
thread2.join();

return 0;
}

  



编译主程序:

g++ -Wall test.cpp -o test -pipe -pthread -ldl -Wl,--export-dynamic -lboost_system -lboost_thread -L[boost库目录]

这边一定要加“-Wl,--export-dynamic”以便导出主程序的符号供动态库回调。

编译成功后生成 test 可执行文件,运行:

#./test
run
12
12
12
12
....

假设这时我们修改 make_so.h


#include "say.h"
extern "C"
{
void Enter(const std::string &str)
{
Say::instance().Sth(str
+ ",ab"); //修改输出
}
}

  

g++ make_so.cpp -fPIC -shared -pthread -rdynamic -lboost_thread -lboost_system -o test.so -L[boost库目录]
重新编译后,这时我们主程序会马上响应,输出:

12,ab
12,ab
12,ab
12,ab
....

说明热替换是成功的。

在实际运用中,当要替换动态库时可以在程序中使用
DoSth::instance().Reload();
具体方法很多,可以通过socket、中断信号、监听文件系统、定时更新等方式。

load_so.h 代码:



#ifndef LOAD_SO_H
#define LOAD_SO_H

#include
<iostream>
#include
<boost/shared_ptr.hpp>
#include
<dlfcn.h>
using namespace std;

class LoadSo
{
typedef
void (*Func)(const std::string &suid);
public:
LoadSo(
const char * so_file)
{
load(so_file);
}

~LoadSo()
{
Close();
}

void Close()
{
dlclose(m_handle);
}

void run( const std::string &str )
{
m_func(str);
}

private:
bool load(const char * so_file)
{
m_handle
= dlopen(so_file, RTLD_LAZY);
if (!m_handle)
{
std::
string error("Cannot open library: ");
throw std::runtime_error(error + dlerror());
return false;
}

dlerror();
m_func
= (Func) dlsym(m_handle, "Enter");
const char *dlsym_error = dlerror();
if (dlsym_error)
{
dlclose(m_handle);
std::
string error("Cannot load symbol: ");
throw std::runtime_error(error + dlsym_error);
return false;
}
return true;
}

Func m_func;
void* m_handle;
};
typedef boost::shared_ptr DynamicSo_ptr;

#endif

  


说在最后,我在一些测试中发现有时并不自动切换到新的动态库的情况,另一个做法就是备用两个动态库,那么在Reload时在两个so文件之间切换,这样可以确保更新,额外给自己找的好处是可以保留旧版本的so。

http://lajabs.net

posted @ 2011-07-19 11:57 lajabs 阅读(991) 评论(0) 编辑

引用之前发表过的文章: http://lajabs.blog.cd/?p=342
针对如下错误:
terminate called after throwing an instance of 'boost::exception_detail::clone_impl<boost::exception_detail::error_info_injector >'
what(): boost::lock_error

在之后不断地资料查找中发现官方解释如下,这是一系列的锁检测机制:

Checked Locking Strategy

With a checked locking strategy, when a thread attempts to acquire a lock on the mutex object for which the thread already owns a lock, the operation will fail with some sort of error indication. Further, attempts by a thread to unlock a mutex object that was not locked by the thread will also return some sort of error indication. In Boost.Thread, an exception of type boost::lock_error would be thrown in these cases.

Boost.Thread does not currently provide any mutex objects that use this strategy.

Unchecked Locking Strategy

With an unchecked locking strategy, when a thread attempts to acquire a lock on a mutex object for which the thread already owns a lock the operation will deadlock. In general this locking strategy is less safe than a checked or recursive strategy, but it's also a faster strategy and so is employed by many libraries.

Boost.Thread does not currently provide any mutex objects that use this strategy.

Unspecified Locking Strategy

With an unspecified locking strategy, when a thread attempts to acquire a lock on a mutex object for which the thread already owns a lock the operation results in undefined behavior.

In general a mutex object with an unspecified locking strategy is unsafe, and it requires programmer discipline to use the mutex object properly. However, this strategy allows an implementation to be as fast as possible with no restrictions on its implementation. This is especially true for portable implementations that wrap the native threading support of a platform. For this reason, the classes boost::mutex, boost::try_mutex and boost::timed_mutex use this locking strategy despite the lack of safety.

上面简单的意思是:
使用Checked Locking Strategy (检测策略)时发生死锁会抛出“boost::lock_error ”的异常。
boost没有提供Unchecked Locking Strategy (非检测策略) 相关的锁。
使用Unspecified Locking Strategy (未定义策略)结果是未定义的。

注意boost::mutex使用的是Unspecified Locking Strategy 未定义的检测机制,也就是说它既可能死锁,也可能是抛出“boost::lock_error ”的异常,这对程序的调试会带来许多不确定因素。

所以建议根据情况尽可能地使用recursive_mutex,它使用的是Recursive Locking Strategy,具体描述如下:

With a recursive locking strategy, when a thread attempts to acquire a lock on the mutex object for which it already owns a lock, the operation is successful. Note the distinction between a thread, which may have multiple locks outstanding on a recursive mutex object, and a lock object, which even for a recursive mutex object cannot have any of its lock functions called multiple times without first calling unlock.

Internally a lock count is maintained and the owning thread must unlock the mutex object the same number of times that it locked it before the mutex object's state returns to unlocked. Since mutex objects in Boost.Thread expose locking functionality only through lock concepts, a thread will always unlock a mutex object the same number of times that it locked it. This helps to eliminate a whole set of errors typically found in traditional C style thread APIs.

Classes boost::recursive_mutex, boost::recursive_try_mutex and boost::recursive_timed_mutex use this locking strategy.

它就是常说的可重入的递归锁,可以最大程度避免死锁的发生,当然需要付出一些效率上的代价(基本可以接受),但对于保证整个程序健壮性而言是非常值得的。

使用方法:

boost::recursive_mutex mtx;
boost::recursive_mutex::scoped_lock
lock(mtx);

  

posted @ 2011-07-19 11:44 lajabs 阅读(364) 评论(0) 编辑

php做为脚本语言,有时也有特殊的需求对外连接到其它服务器发送消息,简单的方法可以直接使用socket扩展,比如连接一个TCP服务器:



$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if($socket)
{
socket_connect(
$socket, '127.0.0.1', 8081);
$pack = 'hello.'; //发送的内容
socket_send($socket, $pack, strlen($pack), 0);
socket_recv(
$socket, $buffer, 1024, 0);
echo $buffer; //接收的内容
}

  


这些socket功能已经可以满足大多数对外连接的需要了,但面对一些连接特别频繁的环境就显得有些力不从心,系统频繁地建立和销毁连接的开销是很大的,这时需要一种持久连接,说到持久连接,很多人可能会马上想到mysql_pconnect这类函数(有点谈虎色变),由于PHP的运行机制造成持久连接一直有着这样那样的问题(不细谈),其实对于目前高版本的PHP,启用php-fpm运行在fast-cgi下的持久连接使用还是比较理想的。

php很早就提供了一个这样的函数:pfsockopen,可能更多人对于fsockopen更了解,而使用上则大同小异。

简单地用例子说明:


$socket = pfsockopen('127.0.0.1',8081,$errno, $errstr);
if($socket)
{
$pack = 'hello.';
fwrite($socket, $pack); //fput 、fget
$buffer = fread($socket, 1024);
echo $buffer;
}

  

php建立了一个和目标服务器8081端口连接,当PHP脚本执行完成后不会断开连接,并在下一次脚本运行时重新使用这个连接,
使用php-fpm环境下会建立与其进程数相同的连接数,这样就类似形成一个“连接池”。

posted @ 2011-07-19 11:42 lajabs 阅读(238) 评论(0) 编辑

此前发表过一篇关于使用信号量做php进程同步的例子:http://lajabs.net/?p=159,其主要特点就是高效简单,缺点是对信号量的管理比较复杂,在异常情况下可能未及时释放锁定造成下一次访问的死锁。
网上也有很多关于使用文件锁的,其实在高并发下性能上都很不理想。
遂考虑使用共享内存方式实现一个属于php的“自旋锁”,主要特点是:
1、检测和避免死锁
2、并可以自定义锁定超时
3、可以在运行结束后自动释放锁定
4、可搜集分析锁竞争和锁等待情况

/**
* php"自旋锁"功能 (PHP Spinlock)
* 用于php进程同步时使用
*
* Author : lajabs.net 2011/7/15
*/

class slock
{
/**
* $lock_timeout 设置等待回旋次数
* $lock_wait_func 设置等待机制,本例使用usleep+mt_rand随机等待,随机等待有利错开多个竞争
* $add_func 这里设置添加锁标志机制,本例使用PHP的APC组件apc_add函数,apc_add设定锁占有上限为5s,避免永久占有
* $del_func 这里设置删除锁标志机制,本例使用PHP的APC组件apc_delete函数
*/
private $locks;
private $lock_timeout = 200;
private $lock_wait_func;
private $add_func;
private $del_func;
public function __construct()
{
$this->add_func = function($mutex)
{
return apc_add('sl:'.$mutex,1,5);
};

$this->del_func = function($mutex)
{
return apc_delete('sl:'.$mutex);
};

$this->lock_wait_func = function()
{
usleep(mt_rand(10000,50000));
};
}

public function __destruct()
{
$this->clean();
}

/*================================= 分割线 ==========================*/

/**
* 清除当前所有设置的锁,在当前的php进程中可以设置多个锁
*/
public function clean()
{
if($this->locks)
{
foreach($this->locks as $lock => $tmp)
call_user_func($this->del_func ,$lock);
$this->locks = null;
}
}

/**
* 新建立一个锁
* 首先会判断锁定标志是否已经定义,如果已锁定则判定为死锁
* 其次使用apc共享内存方式add一个锁标志,如果失败则进入等待时间,直到超时
*/
public function lock($mutex)
{
if($this->locks[$mutex]!=null)
{
throw new Exception('Detected deadlock.');
return false;
}

while(call_user_func($this->add_func ,$mutex) == false)
{
++$i;
if($i > $this->lock_timeout)
{
throw new Exception('lock timeout.');
return false;
}
call_user_func($this->lock_wait_func);
}
$this->locks[$mutex] = 1;
return $mutex;
}

/**
* 手动释放锁,一般不用,
* 在当前对象析构时会自动释放所有锁
*/
public function release($mutex)
{
if($mutex == false) return false;
unset($this->locks[$mutex]);
call_user_func($this->del_func ,$mutex);
return true;
}
}

/*
Example:

$lock = new slock();
echo 'Start:',date('H:m:s'),',';
$lock->lock(123);
sleep(3);
echo 'Stop:',date('H:m:s');
//$lock->release(123);
*/

  

运行上述测试例子,测试同步情况:
在两个浏览器打开运行这个代码,分别输出,
浏览器1:
Start:13:07:19,Stop:13:07:22
浏览器2:
Start:13:07:19,Stop:13:07:25

第一个浏览器显示程序运行了3秒后结束。
第二个浏览器等待3秒后开始运行,总共耗时6秒,测试通过。

posted @ 2011-07-19 11:28 lajabs 阅读(305) 评论(0) 编辑