作者:Snake
发布时间:March 15, 2012
分类:PHP
No Comments
大蛇写这篇文章是因为TIPI上关于PHP写时复制(Copy-On-Write)问题被同事发到群里,引起了我的兴趣。下面就把这个问题说出来,大家想想为什么:
<?php
$foo['love'] = 1;
$bar = &$foo['love'];
$tipi = $foo;
$tipi['love'] = '2';
echo $foo['love']; // 输出 2
相信很多人会认为这是一个BUG,为什么$foo['love']的值会被改变?在官方的邮件列表中,这个问题也被讨论烂了,与其说是特性,我更原因说它是个BUG。因为一切有可能挖坑的动作都应该被规避。
为什么会有这样的差异?我们从这里谈开去。
变量类型:
PHP的变量类型有8种,其中NULL和resource是特殊类型,我们常用的有简单类型:int, float, string, boolean,和复合类型:array, object。
何谓简单类型?何谓复合类型?我们来看一看PHP是怎样实现变量的。
typedef union _zvalue_value {
long lval; /* long value */
double dval; /* double value */
struct {
char *val;
int len;
} str;
HashTable *ht; /* hash table value */
zend_object_value obj;
} zvalue_value;
typedef struct _zval_struct zval;
struct _zval_struct {
zvalue_value value;
zend_uint refcount__gc;
zend_uchar type;
zend_uchar is_ref__gc;
};
如上,定义了联合体zvalue_value和结构体zval。
zval是变量的结构,而zval_value就是zval的值。
在zval中type表示数据的类型,他们是:
#define IS_NULL 0
#define IS_LONG 1
#define IS_DOUBLE 2
#define IS_BOOL 3
#define IS_ARRAY 4
#define IS_OBJECT 5
#define IS_STRING 6
#define IS_RESOURCE 7
3(IS_BOOL)及以下类型可以通过联合体 zvalue_value其中的一项来表述,布尔和整型保存在lval中,浮点和双精度浮点保存在double中,NULL是无需保存的,只要type设为IS_NULL就行了。剩下的则麻烦点,比如字符串型的存在struct str中,分别保存字符串和长度,也就是说我们使用 strlen会直接返回长度而无需重新计算字符串长度。数组保存在哈希表ht中,而对象则保存在 obj中。
复合型的变量,如$array['foo']=888;,$array的类型是 IS_ARRAY,而$array['foo']的类型则是IS_LONG,在$array中实际保存的并非888,而是指向$array['foo']的指针。也就是说array的值实际上是一个指针的集合。
我们再看zval中的refcount__gc和 is_ref__gc。refcount__gc是一个计数器,而is_ref__gc则表示该变量是否为引用。那么
$a = &$b; $c=1;
的情况下,$a和$b的is_ref__gc值均为1;$c的is_ref__gc的值为0。
那么refcount__gc在什么时候用呢?那我们接下来说说变量的回收机制。
变量在unset的时候会被注销,那么他的值占用的内存是否马上释放呢?实际上不是,refcount__gc这个计数器就是做这个用途的。
PHP有个特性叫做写时复制(Copy-On-Write),例如:
$a = 1;
$b = $a;
这个时候PHP并不会为$b申请一块新的内存,而是将$a的refcount__gc这个计数器加1,再将$a赋值给$b。当我们echo $b时,实际上读到的是同$a指向同一个内存地址的值。当我们执行:
$b=2;
的时候,PHP会先检查$b是否为引用(这里不是),然后再将$b与$a共同的refcount__gc减1并判断是否为0(这里不是,而是1),那么PHP会重新为$b申请一块新内存,复制$a的值,再修改为2,这个时候$b的refcount__gc发生一次自增,变为1。
那么PHP在unset($b)时所做的就是判断它的refcount__gc在减1后是否为0,如果是,那么则回收(实际上也并没有释放内存,只是放到缓冲区,等满了再释放);如果不是,则只把$b从符号表删除。
=============== 休息,休息一下 ======== 一休割 ===============
那么我们来分析以下为什么会出现文章开头的那个问题,下面一行行分析:
$foo ['love'] = 1;
// $foo: refcount=1; isref=0;
// ->love: refcount=1; isref=0;
$bar = &$foo['love'];
// $bar: refcount=2; isref=1;
// $foo->love: refcount=2; isref=1;
// $foo: refcount=1; isref=0;
$tipi = $foo;
// $foo: refcount=2; isref=0;
// $foo->love: refcount=2; isref=1;
// $tipi: refcount=2; isref=0;
// $tipi->love: refcount=2; isref=1;
// 注意,这一步复合类型(array)$foo的refcount自增到2,而$foo['love']还是数组的hashtable指向的另一块内存地址,它并不会被复制
$tipi['love'] = '2';
// 这里的$tipi['love']是一个引用,如同$foo['love']一样
echo $foo['love'];
// 所以当$tipi['love']改变以后,这里自然会输出 2
理解了吗?
相信你看完这个分析后也会认为这个是PHP的特色,我也是这么想的。知道真相后似乎要推翻之前的结论——这是个BUG。但是仔细想想,这种坑实际上是不应该出现的,所以我还是坚持最开始的想法——这就是个BUG!当然,见仁见智。
引用不要滥用,因为PHP本身已经对变量做了很好的优化。但是有些时候还是该用,比如你实际上想传址而不是传值。
另外,大蛇要提醒一句,在5.4.0中,动态引用已经被取消了,例如:
function myfunc($var){
$var = 1;
}
myfunc(& $foo)
这里这种写法是会导致错误出现的,正确的用法应该是:
function myfunc(& $var){
$var = 1;
}
myfunc($foo)
好吧,到这里,就到这里,再见吧!
作者:Snake
发布时间:March 9, 2012
分类:PHP,*nix
No Comments
《memcache分布式的一点理解》(http://www.cnblogs.com/mxw09/archive/2011/08/16/2141457.html)
=========================================
上周看到到这篇文章便引起了我的好奇,于是上周一便进行一番查证并在邮件组里分享了下面这篇文章。因为仓促,所以并没有整理的很好,总之先归档了。
=========================================
1. ——“memcache的分布式是通过 Memcache::addServer这个方法实现的”
—— addServer到底做了什么?
addServer指向memcache_add_server,在这个函数中,会有*mmc_object = getThis()来取得当前调用类的实例,然后是判断数据的合法性等,然后由php_mmc_pool_addserver来完成addServer 的实际操作。
在php_mmc_pool_addserver中,初始化了连接句柄(如果已经存在或者是长连接则返回连接句柄),再来把这个句柄传给:mmc_pool_add(pool, mmc, weight);注意这里才使用了权重weight。
然后才有mmc_pool_add把连接句柄保存到连接池,并步进连接池的计数器。
结论:addServer实际上是根据所传配置向连接池中新增连接句柄。
2. 那么Memcache扩展是如何把数据保存到多台Memcached服务器其中的一台的?
/* same as in binary protocol */
#define MMC_OP_GET 0x00
#define MMC_OP_SET 0x01
#define MMC_OP_ADD 0x02
#define MMC_OP_REPLACE 0x03
#define MMC_OP_GETS 0x32
#define MMC_OP_CAS 0x33
#define MMC_OP_APPEND 0x34 /* not supported by binary protocol */
#define MMC_OP_PREPEND 0x35 /* not supported by binary protocol */
PHP_FUNCTION(memcache_set) {
php_mmc_store(INTERNAL_FUNCTION_PARAM_PASSTHRU, MMC_OP_SET);
}
memcache_set调用的是php_mmc_store,首先,数据初始化,做一些逻辑判断,通过mmc_get_pool取得连接池。
我们这里为了方便理解,假设memcache_set的key是一个字符串而非数组。Memcache会以TCP的方式分配一个请求,然后将要存储的值通过mmc_ascii_store或者mmc_binary_store存储到缓冲sendbuf。
在mmc_pool_schedule_key中,调用mmc_pool_find来找一台命中服务器,那么Memcache的分布式就是在这里实现的啦。
在mmc_pool_find中,使用了mmc_hash_find_server,通过某种Hash算法(这里我们放到第三点讲)来返回命中的服务器。
如果命中的服务器连接失败的话,则命中下一台服务器直到成功,否则报错。
如果命中服务器连接成功,则继续到mmc_pool_run来执行“把指定数据通以指定键存储到指定服务器:端口的形式”存储过程。当然在Memcache 3.0以后还有的版本还有冗余数据,这点我们放到下面第四点来讲。
3. Memcache的hash_strategy分为standard、consistent;
默认值"standard"表示使用先前版本的老hash策略。
设为"consistent"可以允许在连接池中添加/删除服务器时不必重新计算key与server之间的映射关系。
Memcache的hash_function 控制将key映射到server的散列函数。
默认值"crc32"使用CRC32算法,而"fnv"则表示使用FNV-1a算法。FNV-1a比CRC32速度稍低,但是散列效果更好。
4. 前面我们说到冗余,怎么冗余?冗余什么?
我们知道如果命中的服务器连接失败,会命中下一台。那么冗余,实际上就是防范于未然,在连接成功以后,再到下一台服务器上进行冗余的存储。
我们在get的时候,如果成功,则在第一台命中的服务器上返回了,如果失败了,则在其后的一台服务器上冗余。
当然,如果集群里面的memcache宕掉很多的话,那么自然会雪崩了。
5. 原文中的“(3) 加入set和get的memcache服务器连接池的连接一样但是顺序不一样,会发现get缓存的时候明明set的所有缓存项都存入了对应的memcache服务器但是很多缓存项就是取不到”这是自然。
因为addServer是维护了一个连接池,先调用这个方法的则先进连接池,后调用的后进连接池。连接池是一个有序队列,自然会出现改变了顺序则无法命中的现象。
6. 你可能不知道,memcache_set是基于TCP的,长连接也无法使用UDP协议。而memcache_get是基于UDP的。
7. 这次翻了很多源码才整理出这篇文章,当然也不乏Memcached扩展和libmemcache。Memcached扩展是基于libmemcache的,至于哪个更好,网上已有分析,我没做过具体的测试,所以不太好发言。
以上。
欢迎斧正。
作者:Snake
发布时间:March 9, 2012
分类:PHP
No Comments
PHP 5.4发布了,兄弟说他代码报错了:
“Call-time pass-by-reference has been removed ”
你准备升级到 PHP 5.4了吗?
大蛇这就来八一八新版本的差异。以下排序依照对开发影响的大小。
========================================================
“Call-time pass-by-reference has been removed ”
调用时引用传递已被废弃。
当我们这样使用函数(或者类)的话,会产生一个error:
foo(& $var);
实际上,这样用本来就是错的,只是之前的错误级别仅仅是Deprecated而已。
而正确的使用方法应该是在函数定义时:
function foo(& $var) {
}
而在调用时直接传参就行了: foo($var);
========================================================
时区(timezone)必须设置
如果没有通过在配置文件中配置date.timezone 或者代码中通过 date_default_timezone_set()的方式来设定时区的话,以前会进行一些推算,而5.4以后则会删除这个特性。取而代之的是使用“UTC”时间。(点击查看什么是“UTF时间”)
========================================================
E_ALL现在包含E_STRICT
众所周知E_STRICT是不包含在E_ALL里面的,所以我们才会有E_ALL & E_STRICT这种写法。
而到了PHP 5.4中,E_ALL将包括E_STRICT。
========================================================
register_globals被移除
相信很多的PHP都对register_globals深恶痛绝,而register_globals也被认为是万恶之首,早在4.2的时候,register_globals就默认关闭了,而到了5.4就把register_long_arrays和register_globals移除了。One Less Thing to Worry About。
========================================================
默认字符集变成UTF-8
配置文件中的default_charset由原来的ISO-8859-1变成UTF-8。
========================================================
session_is_registered(), session_register() 和 session_unregister() 函数被移除.
这些函数在PHP 5.3中已经被废弃,如果要使用,可以直接对$_SESSION变量用isset(),unset()这样的方式。这些方法被移除后,如果使用会导致error。
========================================================
magic_quotes_gpc和相关的所有函数、ini中的设置
被移除的有配置中的magic_quotes_gpc, magic_quotes_runtime 和 magic_quotes_sybase。而get_magic_quotes_gpc, get_magic_quotes_runtime 虽然被保留了,但是始终会返回 false, set_magic_quotes_runtime 会产生E_CORE_ERROR错误。
========================================================
EXT/SQLITE扩展被移除
别太惊讶了,这次被移除的扩展仅仅是ext/sqlite, 而ext/sqlite3 以及 ext/pdo_sqlite并没有被移除。
以上便是这次新版本值得注意的地方,下面这里有完整版,不过是随时在变的,可能你在看这边文章点过去并非一开始就是本文所属的这些修改。
http://php.net/ChangeLog-5.php
作者:Snake
发布时间:February 23, 2012
分类:*nix
No Comments
这篇文章是由José M. Aguilar在他卓越的博客(http://www.variablenotfound.com/2007/12/13-consejos-para-comentar-tu-cdigo.html)中以西班牙语的形式首发,其后Timm Martin在获得Aguilar先生的授权下,对该文章进行翻译、修改,并且在DevTopics上(http://www.devtopics.com/13-tips-to-comment-your-code/)发布。
以下13个小技巧可以使得你的代码在长时间内依然能够保持容易理解和维护。
1. 对不同级别的代码进行注释
对于不同级别的代码块,要使用统一的方法来进行注释。例如:
对于每一个类,需要包含一段简明扼要的描述,作者和上一次修改的时间
对于每一个方法,需要包含这个方法的用途,功能,参数以及返回结果
当你在一个团队里面的时候,采用一套注释的标准是非常重要的。当然,使用一种大家都认可的注释约定和工具(例如C#的XML注释和Java的Javadoc)在一定程度上能推动这项任务。
2. 使用段落注释
首先把代码块分解成多个“段落”,每一个段落都执行单一的任务;然后在每一个“段落”开始之前添加注释,告诉阅读代码的人接下来的这段代码是干什么用的
// 检查所有记录都是正确的
foreach (Record record in records)
{
if (rec.checkStatus()==Status.OK)
{
. . .
}
}
// 现在开始进行处理
Context ctx = new ApplicationContext();
ctx.BeginTransaction();
. . .
3. 对齐注释行
对于那些在行末写有注释的代码,应该对齐注释行来使得方便阅读
const MAX_ITEMS = 10; // maximum number of packets
const MASK = 0x1F; // mask bit TCP
有些开发人员使用tab来对齐注释,而另外一些人会用空格来对齐。由于tab在不同的编辑器和集成开发环境中会有所不同,所以最佳的方法是使用空格来对齐注释行。
4. 不要侮辱阅读者的智慧
要避免没用的注释,例如
if (a == 5) //如果a等于5
counter = 0 //把counte设为0
这不单把时间浪费在写没用的注释上面,同时也在分散读者的注意力。
5. 要有礼貌
应当避免没有礼貌的注释,例如“要注意一些愚蠢的用户会输入一个负数”,或者“修正由菜鸟工程师写的愚蠢得可怜的代码而导致的副作用”。这样的注释对于代码的写注释的人来说并没有任何好处,同时你永远都不会知道将来这些注释会被谁来阅读,你的老板,一个客户或者是刚才被你数落的愚蠢得可怜的工程师。
6. 直截了当
不要在注释里面写过多的废话。避免在注释里面卖弄ASCII艺术,写笑话,作诗和过于冗长。简而言之就是保持注释的简单和直接。
7. 使用统一的风格
有些人觉得注释应该让非程序员也能看懂。另外一些人觉得注释需要面对的读者只是程序员。无论如何,正如Successful Strategies for Commenting Code中(http://particletree.com/features/successful-strategies-for-commenting-code/)所说的,最重要的是注释的风格需要统一,并且总是面向相同的读者。就自己而论,我怀疑非程序员是否会去读代码,所以我觉得注释应该面向程序员来写。
8. 在内部使用特殊的标签
当你在一个团队里工作的时候,采用一组一致的标签能帮助不同的程序员沟通。例如,很多团队会采用“TODO”标签来表示一段尚未完成的代码
int Estimate(int x, int y)
{
// TODO: implement the calculations
return 0;
}
标签注释并不会解释代码,它们寻求注意或者是传递信息。但是如果适当地使用这种技术,要记住跟进这段代码并且完成该标签传递的任务。
9. 在写代码的同时添加注释
当你在写代码而且记忆犹新的同时就添加注释。如果等到项目后期才添加注释,会让你事倍功半。“我没有时间写注释”,“我的时间很紧迫”和“项目已经延迟了”,这些都是不写注释的常见借口。有些工程师觉最佳的解决方法是“注释先行”(http://freshmeat.net/articles/view/238/)。例如:
public void ProcessOrder()
{
// Make sure the products are available
// Check that the customer is valid
// Send the order to the store
// Generate bill
}
10. 把自己想象为注释的读者(事实上就是如此)
当你正在给代码写注释的时候,不仅仅为日后维护你的代码的开发者考虑,同时也设想一下如果自己就是注释的读者。Phil Haack(http://haacked.com/archive/2007/06/25/understanding-productivity-differences-between-developers.aspx)曾经说过:
“一旦一行代码被敲到文件中, 你就已经要开始维护那一行代码了。”
所以,我们自己就是好(或者坏)注释的第一个受益者(或者受害者)。
11. 更新代码的时候要更新注释
如果注释没有随着代码的修改而更新,那么这些注释将是毫无意义的。代码和注释需要同步,否则注释只会让维护代码的开发者更加痛苦。需要特别注意的是,一些重构的工具会自动更新代码,但是却没有自动更新注释,那么注释就自然而然地过期作废了。
12. 良好可读性代码是注释的金科玉律
对于很多开发者来说,一个基本的原则就是:让代码自己描述自己。虽然有人怀疑这是由不喜欢写注释的程序员所倡导的一场运动,但是无需解释的代码有很大的好处,这些代码更加容易理解甚至让注释变得没有必要。例如,在我的文章Fluid Interfaces中(http://www.variablenotfound.com/2007/11/interfaces-fluidos-fluent-interfaces.html)就给大家展示了什么是清晰的无需解释的代码。
Calculator calc = new Calculator();
calc.Set(0);
calc.Add(10);
calc.Multiply(2);
calc.Subtract(4);
Console.WriteLine( “Result: {0}”, calc.Get() );
在这个例子里面,注释就像是违反了第4条技巧那样,变得毫无必要。要写出可读性好的代码,你需要使用适当的命名方式(在经典的Ottinger’s Rules中(http://www.objectmentor.com/resources/articles/naming.htm)有阐述),保证恰当的缩进,并且采用编码风格指导。如果代码不遵守这条技巧,那么注释看起来就好像是为自己不好的代码的写道歉信(http://blogs.dovetailsoftware.com/blogs/gsherman/archive/2007/05/11/are-your-code-comments-a-way-to-say-i-m-sorry-for-the-actual-code.aspx)一样。
13. 跟你的同事分享这些技巧
虽然从第10条技巧中我们已经知道了自己就是好注释的得益者,但是这些技巧对于所有的开发者来说都是很有帮助的,尤其是整个团队都有相同共识的情况下。因此,大方地跟你的同事去分享这些技巧,让我们写出更加容易理解和维护的代码。
全文完。
作者:Snake
发布时间:January 30, 2012
分类:*nix
No Comments
nginx默认情况下是不支持pathinfo的,而平常用pathinfo的机会也不多。
Yii这款框架相信看官应该熟悉,如果不熟悉的话那也应该听过的。
Yii是需要使用到pathinfo的,而我又是在Ubuntu下做开发的,所以还是要解决这个问题的。
在网上随便一搜索“nginx pathinfo”就出来一堆,复制粘贴后问题仍然得不到解决,所以只能一行行改来看看了。
看看配置文件,在nginx/site-enable/default中有
location ~ \.php$ {
...
}
我们首先定义2个变量
set $path_info ""; # pathinfo 默认为空
set $real_script_name $fastcgi_script_name;
我们原本是使用$fastcgi_script_name的,然而我们定义一个$real_script_name,他的值为当前的$fastcgi_script_name。
在其后,我们加上
if ($fastcgi_script_name ~ "^(.+?\.php)(/.+)$") {
set $real_script_name $1;
set $path_info $2;
}
用正则匹配$fastcgi_script_name,如果符合.php/xxx这样的格式的,则把从开始到.php这一段赋给$real_script_name。
并且后面这段则是$path_info的值。
然后在后面以
fastcgi_param SCRIPT_FILENAME $document_root$real_script_name;
fastcgi_param SCRIPT_NAME $real_script_name;
fastcgi_param PATH_INFO $path_info;
替换之前的。
fastcgi_param的语法是fastcgi_param KEY VALUE。
这里的KEY被放到PHP的$_SERVER中,而VALUE就是它的值。
例如我们加上 fastcgi_param MY_INFO "this string write to $_SERVER['MY_INFO']";
那么PHP的$_SERVER['MY_INFO']就会是这个字符串。
当然,如果像上面这么操作完以后,肯定是不能正常使用的,我也就是掉到这个坑里面了。
location ~ \.php$ { 这句断定了是.php结尾,那么index.php/xxx的这种形式就肯定不会执行这段了。
所以我们要去掉php后面的 $ 。
这句应该是 location ~ \.php { ,结果才会像我们想象的那样。
- 1
- 2
- 3
- 4
- ...
- 22
- »