分类目录归档:程序相关

C,Python,环境配置等

锁机制概述

本文主要回答如下问题:

  • 什么是锁机制?
  • 锁机制的作用是什么?
  • 锁机制的类型有哪几种?

关键字:并发、并发控制、乐观锁、悲观锁

在我们常见的程序设计、操作系统和数据库等领域,并发是非常棘手的问题之一。而并发在操作系统、数据库等领域,最终还是会体现在软件开发(代码实现)上。并发的主要纠结点在于对共享资源的处理,虽然现在有事务来处理并发,但是这只是在一定程序上缓解了并发的问题,并没有彻底解决,比如跨事务的并发。

在软件开发过程中,并发控制是确保及时纠正由并发操作导致的错误的一种机制。并发控制主要采用时间戳、乐观并发控制和悲观并发控制等技术手段来实现。而今天我们要说的锁机制主要是指后面的两种技术手段:乐观并发控制和悲观并发控制,他们分别对应乐观锁和悲观锁。锁机制是管理对共享资源的并发访问机制。

乐观锁并不是纯粹意义上的锁,它可以理解为冲突检测,属于事后的操作,其中一种实现是依赖数据版本记录机制。在数据源增加一个版本标记,当请求方读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据源中对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据源当前版本号,则予以更新,否则认为是过期数据。比如某个共享数据被并发访问修改,在各个请求方获取了数据后,请求方都会被分配一个相同版本,如果有一个请求方提交了修改,则将原始数据版本增加1,则其它请求方再次提交修改时,由于提交的版本小于当前版本则显示冲突。除了基于数据版本的控制外,还有包含对更新时间的控制,对不同字段的对比控制等。

悲观锁可以理解为冲突避免,属于事前的操作,即不让并发修改的操作发生,减少并发出现。当一个用户访问一个共享对象时锁住它,即先获得锁,此时其它用户无法访问此对象,当对共享对象的操作完成以后要为被它封锁的对象解锁,此时其它对象才能访问此共享对象。如果此时一个用户一直占着一个资源不放,其它所有用户都只能永远等待,此时可能需要引入其它机制来防止这种情况的发生。

悲观锁减少了并发的程序,而乐观锁在一定程度上会更加自由一些,其在获取资源时是不受限制的,仅在提交的时候才会有限制。当需要在悲观锁与乐观锁之间抉择时,可以考虑如下两个点:

  • 冲突的频率 如果冲突少,通常可以选择乐观锁,这样可以获得更多的并发性,但是此时也需要考虑冲突的严重性,如果系统不能容忍冲突的出现,则需要考虑牺牲并发性,使用悲观锁
  • 冲突的严重性 如果冲突所产生的后果比较严重或者为用户不能容忍,需要使用悲观锁

无论是乐观锁还是悲观锁都存在其优点和缺点,只使用某一种机制都会产生其它问题,可以考虑将这两种锁放一起使用,或者提供两种机制,供用户选择,默认使用乐观锁。

Yii框架的组件行为管理机制和Mix-in

Yii框架的组件行为管理机制和Mix-in

本文包括以下内容:

  • Yii框架的组件行为管理机制介绍
  • Ruby、PHP5.4和Mix-in

在Yii框架的官网,我们可以看到关于Behaviors & events的介绍: Behaviors are simply a way of adding methods to an object.

我们看官网上的使用示例:

class SomeClass extends CBehavior{
    public function add($x, $y) { return $x + $y; }
}

class TestComponent extends CComponent {
}

$test_comp = new TestComponent();
$test_comp->attachbehavior('blah', new SomeClass);
$test_comp->add(2, 5);

在TestComponent类的对象创建的后,我们可以通过调用attachbehavior给对象添加新的方法。

通过其源码(在base/CComponent.php)可以知道它是通过在组件类内部以私有变量的方式存储这些添加的方法所在的对象, 通过魔术方法__call,当调用一个未定义的方法时需要调用__call方法的特性,遍历所有通过attachbehavior方法添加进来的对象, 并判断此对象是否禁用并且此对象是否存在需要调用的方法,如果存在则调用。

此种实现方式存在如下一些问题:

  • 如果多个对象存在相同的方法,则程序调用时永远会调用第一次添加进去的方法
  • 如果我们只是需要某个对象中的某个方法,但是在存储上需要将整个对象添加到列表中

也许你会觉得这些都是一些如果,都是一些假设,可能不会出现,这有些像众所周知的goto语句问题,如果用得好,这是一个利器,如果用得不好,可能会给你带来痛苦。 Yii框架中的这种机制实现运行时的方法绑定,虽然类的属性和实例参数仍然归属于其它类和对象。

在官方说明中也提到了这是一种类似于ruby语言的实现方式,如果我们用Ruby实现上面的方法该如何写呢?如下:

module SomeClass
    def add(x, y)
        return x + y
    end
end

class TestComponet
    include SomeClass
end

test = TestComponet.new
puts test.add(10, 20)

非常简单的实现了类的重用,我们知道在PHP中,接口是可以多继承的,但是接口只是形态上的多继承,是一种对于类实现的约束,是一种规格。 如果要实现这种类的重用,Ruby受Lisp的影响引入了Mix-in,在PHP5.4引入了trait关键字。

在Ruby中Mix-in的关键字是module,而在即将推出的PHP5.4,其对应的关键字是trait; 如果要复用这个定义的类,在Ruby中使用include,而在PHP5.4中使用use。如下PHP代码:

<?PHP
trait SomeClass {
    public function add($x, $y) {
        return $x + $y;
    }
}

class TestComponent {
    use SomeClass;
}

$obj = new TestComponent();
echo $obj->add(10, 20);

对于Mix-in类,有两个约束:

  • 不能单独生成实例
  • 不能继承其它的普通类

如果实例这个类程序执行会显示:

Fatal error: Cannot instantiate trait SomeClass...

如果从其它普通类继承会显示:

Fatal error: A trait (SomeClass) cannot extend a class ...

如果要查找这两个约束的源码实现,可以直接在源码中搜索Cannot instantiate trait和cannot extend a class。 从搜索可以看出:

  • 不能单独生成实例的检测是在new关键字的中间代码执行时执行的,在Zend/zend_vm_execture.h文件
  • 不能继承的约束是在编译成中间代码的过程中实现的,在 Zend/zend_compile.c文件

在面向对象编程语言,Mix-in是一个提供了一些被用于继承或在子类中重用的功能的类,它类似于一种多继承, 但是实际上它是一种中小粒度的代码复用单元,而不直接用于实例化。 虽然这不是一种专业的方式进行功能复用,这在实现多继承的同时,在一定程序上避免了多继承的明显问题。 一如Yii的组件行为管理机制,也是另外一种取巧的Mix-in实现。

使用Bison和re2c构建词法分析和语法分析器

使用说明: 本文需要读者对C语言有一定的基础,对于re2c和bison有一些了解,最好也熟悉linux命令

我们在前面介绍了PHP的语法分析器-Bison入门PHP的词法解析器:re2c,那么如何将re2c与bison集成在一起的呢? 我们以一个从PHP源码中剥离出来的示例来说明整个过程。这个示例的功能与词法分析器的示例类似,作用都是识别输入参数中的字符串类型。 本示例是在其基础上添加了语法解析过程。 首先我们看这个示例的语法文件:demo.y

%{
#include <stdio.h>
#include "demo_scanner.h"
extern int yylex(znode *zendlval);
void yyerror(char const *);

#define YYSTYPE znode   //关键点一,znode定义在demo_scanner.h   
%}

%pure_parser    //  关键点二

%token T_BEGIN
%token T_NUMBER
%token T_LOWER_CHAR
%token T_UPPER_CHAR
%token T_EXIT
%token T_UNKNOWN
%token T_INPUT_ERROR
%token T_END
%token T_WHITESPACE

%%

begin: T_BEGIN {printf("begin:\ntoken=%d\n", $1.op_type);}
     | begin variable {
        printf("token=%d ", $2.op_type);
        if ($2.constant.value.str.len > 0) {
            printf("text=%s", $2.constant.value.str.val);
        }
        printf("\n");
}

variable: T_NUMBER {$$ = $1;}
|T_LOWER_CHAR {$$ = $1;}
|T_UPPER_CHAR {$$ = $1;}
|T_EXIT {$$ = $1;}
|T_UNKNOWN {$$ = $1;}
|T_INPUT_ERROR {$$ = $1;}
|T_END {$$ = $1;}
|T_WHITESPACE {$$ = $1;}

%%

void yyerror(char const *s) {
    printf("%s\n", s);
}

这个语法文件有两个关键点:

1、znode是复制PHP源码中的znode,只是这里我们只保留了两个字段,其结构如下:

typedef union _zvalue_value {
    long lval;                  /* long value */
    double dval;                /* double value */
    struct {
        char *val;
        int len;
    } str;
} zvalue_value;

typedef struct _zval_struct {
    /* Variable information */
    zvalue_value value;     /* value */
    int type;    /* active type */
}zval;

typedef struct _znode {
    int op_type;
    zval constant;
}znode;

这里我们同样也复制了PHP的zval结构,但是我们也只取了关于整型,浮点型和字符串型的结构。 op_type用于记录操作的类型,constant记录分析过程获取的数据。 一般来说,在一个简单的程序中,对所有的语言结构的语义值使用同一个数据类型就足够用了。比如在前一小节的逆波兰记号计算器示例就只有double类型。 而且Bison默认是对于所有语义值使用int类型。如果要指明其它的类型,可以像我们示例一样将YYSTYPE定义成一个宏:

#define YYSTYPE znode

2、%pure_parser 在Bison中声明%pure_parse表明你要产生一个可重入(reentrant)的分析器。默认情况下Bison调用的词法分析函数名为yylex,并且其参数为void,如果定义了YYLEX_PARAM,则使用YYLEX_PARAM为参数, 这种情况我们可以在Bison生成的.c文件中发现其是使用#ifdef实现。

如果声明了%pure_parser,通信变量yylval和yylloc则变为yyparse函数中的局部变量,变量yynerrs也变为在yyparse中的局部变量,而yyparse自己的调用方式并没有改变。比如在我们的示例中我们声明了可重入,并且使用zval类型的变更作为yylex函数的第一个参数,则在生成的.c文件中,我们可以看到yylval的类型变成

一个可重入(reentrant)程序是在执行过程中不变更的程序;换句话说,它全部由纯(pure)(只读)代码构成。 当可异步执行的时候,可重入特性非常重要。例如,从一个句柄调用不可重入程序可能是不安全的。 在带有多线程控制的系统中,一个非可重入程序必须只能被互锁(interlocks)调用。

通过声明可重入函数和使用znode参数,我们可以记录分析过程中获取的值和词法分析过程产生的token。 在yyparse调用过程中会调用yylex函数,在本示例中的yylex函数是借助re2c生成的。 在demo_scanner.l文件中定义了词法的规则。大部分规则是借用了上一小节的示例, 在此基础上我们增加了新的yylex函数,并且将zendlval作为通信变量,把词法分析过程中的字符串和token传递回来。 而与此相关的增加的操作为:

SCNG(yy_text) = YYCURSOR;   //  记录当前字符串所在位置
/*!re2c
  <!*> {yyleng = YYCURSOR - SCNG(yy_text);} //  记录字符串长度

main函数发生了一些改变:

int main(int argc, char* argv[])
{
    BEGIN(INITIAL); //  全局初始化,需要放在scan调用之前
    scanner_globals.yy_cursor = argv[1];    //将输入的第一个参数作为要解析的字符串

    yyparse();
    return 0;
}

在新的main函数中,我们新增加了yyparse函数的调用,此函数在执行过程中会自动调用yylex函数。

如果需要运行这个程序,则需要执行下面的命令:

re2c -o demo_scanner.c -c -t demo_scanner_def.h demo_scanner.l
bison -d demo.y
gcc -o t demo.tab.c demo_scanner.c
chmod +x t
./t "<?php tipi2011"

相关代码下载请移步TIPI项目