月度存档: 六月 2011

使用chrome原生代理api的代理管理插件

今天在台球室等桌的时候上GR,突然发现了这文章,http://www.chromi.org/archives/12679。真是大囧啊。。。我前两天就在改proxy switchy!的代码,想使它支持chrome原生代理,界面什么的都保持不变。到今天看到这文章的时候主要功能是已经完成了,不过还有点bug。。试了试这个扩展。。估计作者和我思路一模一样。。看来我的第一个chrome插件是夭折了。。。@@。最后附上插件链接。http://code.google.com/p/switchyplus/。囧囧囧囧!!!

Windows下chrome+proxy switchy!使用独立代理自动翻墙

本来如果chrome使用和firefox一样的独立代理设置的话,这种文章完全没有必要出现,无奈windows下面chrome使用的是ie的代理,而ie的代理又非常地乱,使得设置下来各种bug,比如有可能每次在线更新地址pac都会被清空,因而要把pac更新一次,再设为只读,比如有时候明明auto-switch模式,设置也正确但流量全是不走代理等等。今天折腾了一小时发现了一个比较完美的方法。

chrome在四月份发布的API中有独立代理设置的API,但proxy switchy!这种插件肯定是不会使用这种实验性质的api的,那有没有办法让它们共存呢?网上找了挺久后发现是有方法的,配置好后proxy switchy!负责更新需要翻墙的list,然后使用独立代理配置浏览器进行翻墙。

首先配置proxy switchy!,online rule list我找了一个新的list,内容取自autoproxy,但修复过兼容性,地址在这里 。然后随便切换成auto-switch模式上下网,就会在proxy switchy!的配置文件夹下面生成SwitchyAuto.pac,这就是后续步骤需要的东西了,proxy switchy!的作用就是提供这个pac文件。

这里可以下到google 提供的使用独立代理设置的examples。在extensions界面里打开developer mode,安装这个插件,完成后配置代理,使用之前的pac脚本,大功告成。

python中yield的实现

Python中的iter可是个好东西,时常都会要用到,不过《Python源码剖析》中只提到了for中的iter,对另一种形式,也就是包含yield語句的函数未做研究。这篇文章就讲讲yield是如何实现的。

其实最后的实现并不困难,但实现yield的部分从源码中找出来可费了我好大功夫。先来看看要研究的范例代码:

#!/usr/bin/python
#encoding:utf8

def iter_func():
  4           0 LOAD_CONST               0 (<code object iter_func at 0xb789a410, file "mod1.py", line 4>)
              3 MAKE_FUNCTION            0
              6 STORE_NAME               0 (iter_func)

    i = 5
  5           0 LOAD_CONST               1 (5)
              3 STORE_FAST               0 (i)
    yield i
  6           6 LOAD_FAST                0 (i)
              9 YIELD_VALUE
             10 POP_TOP
             11 LOAD_CONST               0 (None)
             14 RETURN_VALUE        

for i in iter_func():
  9           9 SETUP_LOOP              22 (to 34)
             12 LOAD_NAME                0 (iter_func)
             15 CALL_FUNCTION            0
             18 GET_ITER
        >>   19 FOR_ITER                11 (to 33)
             22 STORE_NAME               1 (i)

    print i
 10          25 LOAD_NAME                1 (i)
             28 PRINT_ITEM
             29 PRINT_NEWLINE
             30 JUMP_ABSOLUTE           19
        >>   33 POP_BLOCK
        >>   34 LOAD_CONST               1 (None)
             37 RETURN_VALUE

第一眼看到这个编译出来的字节码觉得没有什么滑头,只有一句是不了解的:yield_value,于是认为实现iter估计就是在这句里面了,于是看看yield_value中python虚拟机都做了什么:

...
		case YIELD_VALUE:
			retval = POP();
			f->f_stacktop = stack_pointer;
			why = WHY_YIELD;
			goto fast_yield;

...

fast_yield:
	if (tstate->use_tracing) {
		if (tstate->c_tracefunc) {
			if (why == WHY_RETURN || why == WHY_YIELD) {
				if (call_trace(tstate->c_tracefunc,
					       tstate->c_traceobj, f,
					       PyTrace_RETURN, retval)) {
					Py_XDECREF(retval);
					retval = NULL;
					why = WHY_EXCEPTION;
				}
			}
			else if (why == WHY_EXCEPTION) {
				call_trace_protected(tstate->c_tracefunc,
						     tstate->c_traceobj, f,
						     PyTrace_RETURN, NULL);
			}
		}
		if (tstate->c_profilefunc) {
			if (why == WHY_EXCEPTION)
				call_trace_protected(tstate->c_profilefunc,
						     tstate->c_profileobj, f,
						     PyTrace_RETURN, NULL);
			else if (call_trace(tstate->c_profilefunc,
					    tstate->c_profileobj, f,
					    PyTrace_RETURN, retval)) {
				Py_XDECREF(retval);
				retval = NULL;
				why = WHY_EXCEPTION;
			}
		}
	}

	if (tstate->frame->f_exc_type != NULL)
		reset_exc_info(tstate);
	else {
		assert(tstate->frame->f_exc_value == NULL);
		assert(tstate->frame->f_exc_traceback == NULL);
	}

	/* pop frame */
exit_eval_frame:
	Py_LeaveRecursiveCall();
	tstate->frame = f->f_back;

	return retval;
}

这代码和我想象中的可不太相同,yield_value好像就pop()了一个值(范例中是5),将它设为返回值,再设置why为why_yield,就路到了fast_yield段,而在fast_yield段,由于python初始化线程环境和帧时,tstate->use_tracing和frame->f_exc_type分别为0和NULL,也就是说基本上yield_value就像return一样,直接返回了一个值就跳出了那个大大的switch和外层的for(;;)循环。这就奇怪了,如果返回是5的话,那在call_function的后一句get_iter肯定会出错啊,不仅是返回5不行,返回一个函数也不对,因为函数和tp_iter也为空。

python总要知道iter_func是一个iter这程序才能正常进行下去啊,到底是在哪里标识的呢?make_function 0,call_function 0这两句创建和调用代码和平常的函数一模一样。我又把范例程序里的yield i改成了return i,也没有看出什么来。又一次地仔细检查call_function才发现问题(其实如果早点写个pycparser再查看编译出来的code对象应该就发现了)。

很容易看出来,范例中call_function会走入fast_function通道,而在fast_function中,co->co_flags并不满足条件,因而会调用到PyEval_EvalCodeEx(),如下:

static PyObject *
fast_function(PyObject *func, PyObject ***pp_stack, int n, int na, int nk)
{
	if (argdefs == NULL && co->co_argcount == n && nk==0 &&
	    co->co_flags == (CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE)) {
	        /* do something */
	}

	return PyEval_EvalCodeEx(co, globals,
				 (PyObject *)NULL, (*pp_stack)-n, na,
				 (*pp_stack)-2*nk, nk, d, nd,
				 PyFunction_GET_CLOSURE(func));
}

奥秘就隐藏在这个PyEval_EvalCodeEx中。


PyObject *
PyEval_EvalCodeEx(PyCodeObject *co, PyObject *globals, PyObject *locals,
	   PyObject **args, int argcount, PyObject **kws, int kwcount,
	   PyObject **defs, int defcount, PyObject *closure)
{
	register PyFrameObject *f;
	register PyObject *retval = NULL;
	register PyObject **fastlocals, **freevars;
	PyThreadState *tstate = PyThreadState_GET();
	PyObject *x, *u;

	if (co->co_flags & CO_GENERATOR) {
		/* Don't need to keep the reference to f_back, it will be set
		 * when the generator is resumed. */
		Py_XDECREF(f->f_back);
		f->f_back = NULL;

		PCALL(PCALL_GENERATOR);

		/* Create a new generator that owns the ready to run frame
		 * and return that as the value. */
		return PyGen_New(f);
	}
}

这下终于发现了,原来python通过 code对象的co_flags来知道这是一个iter,然后根据已经创建的frame来得到一个iter对象。

PyObject *
PyGen_New(PyFrameObject *f)
{
	PyGenObject *gen = PyObject_GC_New(PyGenObject, &PyGen_Type);
	if (gen == NULL) {
		Py_DECREF(f);
		return NULL;
	}
	gen->gi_frame = f;
	Py_INCREF(f->f_code);
	gen->gi_code = (PyObject *)(f->f_code);
	gen->gi_running = 0;
	gen->gi_weakreflist = NULL;
	_PyObject_GC_TRACK(gen);
	return (PyObject *)gen;
}

总结一下,call_function 0这一句就得到一个iter对象,然后压栈,这样在后面的get_iter中就对这个iter对象进行处理。iter对象就是对 frame的一个简单的包装,回想一下get_iter,它要调用对象的type对象中的的tp_iter操作。而PyGen_Type中的tp_iter被设置为PyObject_SelfIter,简单地返回自身。而接下来的for_iter则会调用对象的type中的ip_iternext。

PyTypeObject PyGen_Type = {
	PyVarObject_HEAD_INIT(&PyType_Type, 0)
	"generator",				/* tp_name */
	sizeof(PyGenObject),			/* tp_basicsize */
	/* ... */
	PyObject_SelfIter,			/* tp_iter */
	(iternextfunc)gen_iternext,		/* tp_iternext */
	/* ... */
};

static PyObject *
gen_iternext(PyGenObject *gen)
{
	return gen_send_ex(gen, NULL, 0);
}

static PyObject *
gen_send_ex(PyGenObject *gen, PyObject *arg, int exc)
{
	PyThreadState *tstate = PyThreadState_GET();
	PyFrameObject *f = gen->gi_frame;
	PyObject *result;

	if (gen->gi_running) {
		PyErr_SetString(PyExc_ValueError,
				"generator already executing");
		return NULL;
	}

	/* Generators always return to their most recent caller, not
	 * necessarily their creator. */
	Py_XINCREF(tstate->frame);
	assert(f->f_back == NULL);
	f->f_back = tstate->frame;

	gen->gi_running = 1;
	result = PyEval_EvalFrameEx(f, exc);
	gen->gi_running = 0;

	/* If the generator just returned (as opposed to yielding), signal
	 * that the generator is exhausted. */
	if (result == Py_None && f->f_stacktop == NULL) {
		Py_DECREF(result);
		result = NULL;
		/* Set exception if not called by gen_iternext() */
		if (arg)
			PyErr_SetNone(PyExc_StopIteration);
	}

	return result;
}

调用iter的next函数时可能有很多情况,这里简化了代码只显示最基本的无参调用。从逻辑上考虑一下,在例子中调用iter_func().next()时,func肯定要从上次yield的地方开始执行起。回头再看看yield_value对应的代码我们可以发现一句

f->f_stacktop = stack_pointer;

这句话将iter对象所对应的frame的f_stacktop置为当前的栈顶。这个f_stacktop是干什么的呢?再在ceval.c里面往上找找就可以发现这样一句

f->f_stacktop = NULL;   /* remains NULL unless yield suspends frame */

因而猜测f_stacktop保存着一个指针,当frame执行的时候,如果它不为空,那将栈顶指针初始化为f_stacktop,而什么情况下需要frame需要在一个不为空的“环境”(就是不按正常的方式在一个空栈上运行)中开始执行呢,答案只有yield。再找找代码发现果然是这样

stack_pointer = f->f_stacktop;

到这里iter对象的next()函数基本就清楚了,而iter的开始、停止等其实也就是调用gen_send_ex。yield的实现基本也就清楚了。

Python GIL在Linux下的实现

《Python源码剖析:深度探索动态语言核心技术》真是好书,讲的深入浅出,语言又风趣。让我有点怨念的就是研究平台是Windows,这一般都没什么问题,不过碰到一些平台相关特性,比如thread时就对不上了。今天看到第15章Python多线程机制,里面对GIL的讲解就全是基于Windows的,自己研究了一下Linux下的源码,做点笔记。

在Python初始化多线程环境这部分Linux和Windows一样,就是把initialized变量设为1,并没有做其它的事情。Linux下的GIL实现有两套,由USE_SEMAPHORES宏决定。由名字就可以看出一种实现是使用信号量,一种是不使用信号量的。主要代码都在Python/thread_pthread.h中。

在Python中,GIL的数据结构为PyThread_type_lock,它是一个指向void的指针,这也是因为不同平台实现不同,无法确定一个具体的类型。对GIL的操作共有4种,分别是PyThread_allocate_lock,PyThread_free_lock,PyThread_acquire_lock,PyThread_release_lock。分别执行创建、销毁、锁、解锁GIL的动作。在Python内部还有一个变量会影响到线程請求GIL时的行为,这就是waitflag,如果waitflag为True,那么线程将被阻塞在請求GIL的操作上,反之非阻塞。下面分别分析两种不同的实现。

一、使用信号量。
使用信号量时的实现相当地简单明了。GIL就是一个信号量(sem_t)。直接上代码(有精简)。

PyThread_type_lock
PyThread_allocate_lock(void)
{
	sem_t *lock;
	int status, error = 0;

	lock = (sem_t *)malloc(sizeof(sem_t));

	if (lock) {
		status = sem_init(lock,0,1);
	}

	return (PyThread_type_lock)lock;
}

void
PyThread_free_lock(PyThread_type_lock lock)
{
	sem_t *thelock = (sem_t *)lock;
	int status, error = 0;

	status = sem_destroy(thelock);

	free((void *)thelock);
}

/*
 * As of February 2002, Cygwin thread implementations mistakenly report error
 * codes in the return value of the sem_ calls (like the pthread_ functions).
 * Correct implementations return -1 and put the code in errno. This supports
 * either.
 */
static int
fix_status(int status)
{
	return (status == -1) ? errno : status;
}

int
PyThread_acquire_lock(PyThread_type_lock lock, int waitflag)
{
	int success;
	sem_t *thelock = (sem_t *)lock;
	int status, error = 0;

        // [1]
	do {
		if (waitflag)
			status = fix_status(sem_wait(thelock));
		else
			status = fix_status(sem_trywait(thelock));
	} while (status == EINTR); /* Retry if interrupted by a signal */

	if (waitflag) {
		CHECK_STATUS("sem_wait");
	} else if (status != EAGAIN) {
		CHECK_STATUS("sem_trywait");
	}

	success = (status == 0) ? 1 : 0;

	return success;
}

void
PyThread_release_lock(PyThread_type_lock lock)
{
	sem_t *thelock = (sem_t *)lock;
	int status, error = 0;

	status = sem_post(thelock);
}

在代码中看到的sem_*开头的函数是符合POSIX的信号量操作函数,可以看到Python其实就是对信号量的操作进行了一下包装,而在[1]处的代码则保证請求锁这个操作不会被信号所中断,看过Unix Networking Programming的应该会对这样的語句很熟悉吧。

二、使用一个自定义结构。这种情况稍微复杂一点点。先来看看在这种情况下GIL是个什么东西。

/* A pthread mutex isn't sufficient to model the Python lock type
 * because, according to Draft 5 of the docs (P1003.4a/D5), both of the
 * following are undefined:
 *  -> a thread tries to lock a mutex it already has locked
 *  -> a thread tries to unlock a mutex locked by a different thread
 * pthread mutexes are designed for serializing threads over short pieces
 * of code anyway, so wouldn't be an appropriate implementation of
 * Python's locks regardless.
 *
 * The pthread_lock struct implements a Python lock as a "locked?" bit
 * and a <condition, mutex> pair.  In general, if the bit can be acquired
 * instantly, it is, else the pair is used to block the thread until the
 * bit is cleared.     9 May 1994 tim@ksr.com
 */

typedef struct {
	char             locked; /* 0=unlocked, 1=locked */
	/* a <cond, mutex> pair to handle an acquire of a locked lock */
	pthread_cond_t   lock_released;
	pthread_mutex_t  mut;
} pthread_lock;

可以看到Python自定义了一个struct,其中的locked属性用来表示GIL是否被锁,lock_released和mut共同来实现解锁和锁操作。为什么要这样做呢?注意代码中的注释,可以看到pthread_mutex在两种情况下的行为是不可以预测的:一个线程再次請求被它占有的锁和請求解锁属于另一个线程的锁。我又找了找这方面的资料(链接),发现pthread_mutex有一个kind属性,它可能是fast,recursive或error checking。不同kind属性的pthread_mutex在上述两个操作下的行为是不相同的,而这几个属性都是NP,也就是Non-Portable的,并不通用。在注释中还提到了设计理念的原因,不过这个我就没有太多研究了。接下来看看具体的对GIL的操作。

PyThread_type_lock
PyThread_allocate_lock(void)
{
	pthread_lock *lock;
	int status, error = 0;

	lock = (pthread_lock *) malloc(sizeof(pthread_lock));
	if (lock) {
		memset((void *)lock, '\0', sizeof(pthread_lock));
		lock->locked = 0;

		status = pthread_mutex_init(&lock->mut,
					    pthread_mutexattr_default);

		status = pthread_cond_init(&lock->lock_released,
					   pthread_condattr_default);
	}

	return (PyThread_type_lock) lock;
}

void
PyThread_free_lock(PyThread_type_lock lock)
{
	pthread_lock *thelock = (pthread_lock *)lock;
	int status, error = 0;

	status = pthread_mutex_destroy( &thelock->mut );

	status = pthread_cond_destroy( &thelock->lock_released );

	free((void *)thelock);
}

可以看到创建和销毁GIL的代码还是非常地直观地,就是分别地创建和销毁其中的lock_released和mut。請求锁和解锁的操作稍微绕了一点点弯,如下。


int
PyThread_acquire_lock(PyThread_type_lock lock, int waitflag)
{
	int success;
	pthread_lock *thelock = (pthread_lock *)lock;
	int status, error = 0;

	status = pthread_mutex_lock( &thelock->mut );
	success = thelock->locked == 0;

	if ( !success && waitflag ) {
		/* continue trying until we get the lock */

		/* mut must be locked by me -- part of the condition
		 * protocol */
		while ( thelock->locked ) {
			status = pthread_cond_wait(&thelock->lock_released,
						   &thelock->mut);
		}
		success = 1;
	}
	if (success) thelock->locked = 1;
	status = pthread_mutex_unlock( &thelock->mut );

	if (error) success = 0;
	return success;
}

void
PyThread_release_lock(PyThread_type_lock lock)
{
	pthread_lock *thelock = (pthread_lock *)lock;
	int status, error = 0;

	status = pthread_mutex_lock( &thelock->mut );

	thelock->locked = 0;

	status = pthread_mutex_unlock( &thelock->mut );

	/* wake up someone (anyone, if any) waiting on the lock */
	status = pthread_cond_signal( &thelock->lock_released );
}

具体每一步各个参数返回什么值可以自己手动拿张纸画画,还是很好推的。直接给出结论:GIL在上述两种情况下的动作分别是:当一个线程试图去锁定已经被它锁定过的GIL时,阻塞(waitflag == 1)或返回失败(waitflag == 0),分别类似于kind属性为fast 和 error checking的pthread_mutex;当一个线程试图去解锁并不是由它锁定的GIL时,马上返回,并成功解锁,类似于kink为fast的pthread_mutex。至此解析完成。