《现代操作系统》读书笔记——线程

Read more →

源码安装 NSQ

因为业务需要,要用到 NSQ。所以学习了下 NSQ。首先是安装,我在自己电脑上,倾向于源码安装。一是源码安装可以安装最新的代码,二是整个安装过程可以自己掌控。 但是,安装过程中遇到了一些坑。主要还是我对 Go 以及一些衍生工具用的不是特别熟悉,并且在网上搜索到的文章,都是抄来抄去的很多并不能解决我的问题。所以我把整个安装过程记录下来,给自己一个备忘,给别人一个方便。 安装 Go NSQ 是用 Go 写的,所以安装 NSQ 之前,要先安装 Go。 我这里给出安装具体过程的命令。具体可以参考我写的另外一篇文章 从零开始学习 Go ——安装。 echo "export GOROOT=$HOME/.golang/go" >> ~/.bash_profile echo "export GOPATH=$HOME/.golang/path" >> ~/.bash_profile echo "export PATH=$PATH:$HOME/.golang/go/bin" >> ~/.bash_profile echo "export GOROOT_BOOTSTRAP=$HOME/.golang/go1.4" >> ~/.bash_profile source ~/.bash_profile cd ~ mkdir .golang git clone https://github.com/golang/go.git go cp -r go go1.4 cd go1.4 git checkout -b release-branch.go1.4 origin/release-branch.go1.4 cd src ./make.bash cd ../../go git checkout -b release-branch.go1.8 origin/release-branch.
Read more →

从零开始写 PHP 扩展(一)

PHP 是用 C 语言写的。对于每个 PHPer 来说,都有着内心的一种希望写扩展的冲动了吧。然而,缺乏一个很好的切入点。Google 上搜 PHP 扩展开发,大部分都是复制品文章,甚至有些人连操作都没有操作过就搬运在了自己的博客。不过也有几篇好教程,但是都是 PHP 5 时代的产物,隐藏着非常多的坑。我会将我自己慢慢踩坑的过程记录下来,也许这就成了其它人的“教程”了吧。 生成一个扩展 想必很多人已经看到很多网上的教程了。大多都是教我们执行这个命令:$ ./ext_skel –extname=extname。但是,当你 clone 了 PHP 源码后会发现,master 分支下并没有 ext/ext_skel 这个文件。所以,我总结了一下: 如果你是直接下载 PHP 的源码,或者在已经 release 的版本分之下,你可以执行这个命令 $ cd ext $ ./ext_skel –extname=extname 如果你是直接在 master 分支下,只有 ext_skel.php 文件,这个时候你就直接可以执行这个 PHP 文件 $ cd ext $ php ext_skel.php –ext extname 由于我是直接在 master 分支下开发的,所以后面的都是默认在 master 分之下的操作。 生成了扩展之后,我们会看到四个文件和一个文件夹。现在这个阶段,我们只需要用到两个文件,.c 文件和 .h 文件。 一个小坑 在我们生成好扩展之后,我们可以试着编译一下 $ phpize $ ./configure $ make && make test 我们会惊讶地发现,编译的时候会有一个 warning。
Read more →

浅入理解单例模式

问题 恼人的全局变量 在 PHP 中,甚至不只 PHP 中,我们都会用到全局变量,以保存全局状态。可是,往往全局变量是全局共享的,任何地方任何代码都有可能将其覆盖。例如,我们定义一个全局变量叫做 PHONE。我们在某一行代码中,将其定义成了 iPhone,但是我们不小心在另一行代码中将其覆写成了 Nokia。这就非常的尴尬了,因为本来我们并不想它被覆写。 繁琐的参数传递 在一个系统中,我们会定义许多的方法,生成很多的对象。有时候,我们会使用很多的方法,对同一个对象做操作。在不使用全局变量的情况下,我们需要将对象作为参数传入方法中。但是这样传递同一个对象,可能会造成混乱,还可能造成不必要的依赖。 其实我们只需要一个全局可访问的对象就可以解决这个,但是全局变量又会出现我们上面的说的问题。 解决 目标 我们要解决这些问题,我们对这样的对象有下面的几个目标。 这个对象,无论在哪里都能访问,就想全局变量一样。 这个对象,和全局变量不同,不能被覆写。 这个对象,整个系统中只存在一个,对它的修改在整个系统中都能被感知到。 以上的几个目标,就是我们所需要的,也就是单例模式的特征。 UML 实现 class Preference { private static $instance; private $props = []; private __construct() {} public static function getInstance() { if (empty(self::$instance)) { self::$instance = new Preference(); } return self::$instance; } public function setProperty($key, $value) { $this->props[$key] = $value; } public function getProperty($key) { return $this->props[$key]; } } 我们在这里引入了一个私有的构造函数,这样,外部就无法实例化这个对象了。同时,我们使用 getInstance 方法来获取具体的实例,而无法去覆写它,这就达成了第二个目标。
Read more →

浮点数那些事

本文为作者自己的总结的,由于作者的水平限制,难免会有错误,欢迎大家指正,感激不尽。 说起浮点数,大家都是又恨又爱的。爱呢,是因为,只有它可以方便地使用小数;恨呢,是因为它并不能精确地表示小数。 以 PHP 为例:floor((0.1 + 0.7) * 10) 这样一个函数调用,根据数学老师死得晚原理,大家都能得出 8 这个结果。可是事实上呢?它会返回 7。数学老师的棺材板。。。(╯‵□′)╯︵┻━┻ 可是为什么会出现这种情况呢?这就要从浮点数的特性说起了。 万物皆二进制 我们都知道,在计算机中,一切的一切都是二进制表示的。假设一个 4 字节整型的十进制数 8,在大端表示的机器中,表示成 00000000 00000000 00000000 00001000(0x0000008)。将十进制整数转换成二进制数,是非常容易的。可是,小数呢?比如,我们要表示 1.75,该怎么存储在计算机中呢?显然,不能像整数一样存储了。 小数的二进制 让我们回忆一下,在十进制中,小数是怎么计算的。上面的 1.75 我们是这么算的:1 × 10^0 + 7 × 10^-1 + 5 × 10^-2 。那么我们按照相同的规则,来用二进制计算一下小数部分:0.75 = 1⁄2 + 1/4,也就是 1 × 2^-1 + 1 × 2^-2 ,再加上前面的整数部分,那么整个式子就变成了 1 × 2^0 + 1 × 2^-1 + 1 × 2^-2 ,写成二进制形式就是 1.11。所以,1.75 的二进制表示是 1.11。 对于将小数转换为二进制,和整数部分除二取余相反的,是乘二取整。
Read more →

php 内核探秘之 PHP_FUNCTION 宏

本人也只是个初入门的菜鸟,因对技术有着向往,故在“无趣”的工作之余,尽自己所能提升自己。由于我的 C 语言功底也有限,故本文的深度也有限,如有幸得大牛阅读,还望指导一二,小弟感激不尽。 PHP 的函数 作为 PHPer,我们几乎每天都在写函数,我们一定会好奇,那些 PHP 内置的函数,是长什么样子的。如果写过 PHP 扩展的话,一定知道这个宏:PHP_FUNCTION。在定义一个函数的时候,这样来使用这个宏。例如 array_change_key_case,它的定义是这样的:PHP_FUNCTION(array_change_key_case)。没错,就是这么简单。但是,在这个简单的背后,却没有这么简单。 PHP_FUNCTION 追根溯源 宏 相信对这篇文章感兴趣的同学,一定多少对 C 语言以及它的宏定义有一定的了解。如果没有,也不要紧,我这里来简单解释一下,什么是宏。 C 语言中的宏,我认为,可以理解为一种简单的封装。通过宏定义,可以对开发者隐去一些细节,让开发者在使用简单的语法来完成重复的复杂的编码。当然,宏定义还有其它的用途,但是,我们在 PHP_FUNCTION 涉及到的就是这个作用。有下面的代码。 #define TEST(test) void test(int a) TEST(haha) 宏,就是完全的替换,即使用后面的语句替换前面的。那么对于下面的 TEST(haha) 就相当于下面的样子。 void haha(int a) PHP_FUNCTION 的定义 首先,我们要定义函数,这样使用这个宏。 PHP_FUNCTION(array_change_key_case) { // TODO } 我们在 php-src/main/php.h 中找到了下面的定义。 #define PHP_FUNCTION ZEND_FUNCTION 也就是说,这里用 ZEND_FUNCTION 替换了 PHP_FUNCTION 这个宏。所以,我们的定义就相当于变成了这样。 ZEND_FUNCTION(array_change_key_case) { // TODO } 我们继续往下找,因为,这里还是宏,我们并没有看到我们希望看到的代码。我们可以在 php-src/Zend/zend_API.h 中找到下面的定义。 #define ZENDFN(name) zif##name #define ZEND_FUNCTION(name) ZEND_NAMED_FUNCTION(ZEND_FN(name)) #define ZEND_NAMED_FUNCTION(name) void name(INTERNAL_FUNCTION_PARAMETERS) 我们看到,在宏定义中,使用了另外的宏。不要怕,还是一个词,替换。我们按照这样的步骤来。(## 是一个连接符,它的作用是,是将它前面的与后面的,按照字符串的方式连接起来。
Read more →

每日一个 PHP 函数————array_change_key_case

因为已经有文档了,可能有些人觉得我写这个有些多余了。可是并不是每一个 PHPer 都会好好地去阅读文档,自然有一些函数可能都没有听说过(很不幸我也是这其中的一员)。我也希望能通过写这些文章,能够促使我完整地读完文档,同时,能够给其它的 PHPer 一个参考,“啊,原来还有这个函数” 的感觉。同时,我也希望我能通过写这些文章,去阅读各个函数的 C 语言实现。也实现自我驱动地学习。 函数原型 array array_change_key_case ( array $array [, int $case = CASE_LOWER ] ) 该函数的具体作用是,将一个数组中的所有的英文字母转换为大写或小写。 我们可以看到,这个函数接收两个参数,返回一个数组。第一个参数数组没有使用引用的方式,那么说明该函数并不会改变原数组,它会生成新的数组作为返回值。而第二个参数是可选的,它控制着该函数是转换成大写还是小写。默认是转化为小写。 函数使用 第二个参数 函数的第二个参数传入的是一个预定义常量,分别是 CASE_LOWER 和 CASE_UPPER,前者是将 key 转换成小写,也是函数的默认值;后者是将 key 转换成大写。 使用 $arr = [ ‘loWer’ => 1, ]; $toLower = array_change_key_case($arr, CASE_LOWER); // 我认为,不管它的默认值是什么,我们都要写上这第二个参数。我们的代码写出来,是给人看的,不是给机器看的。 // 所以我们的代码应当尽量多的包含语义。 $toUpper = array_change_key_case($arr, CASE_UPPER); var_dump($toLower); var_dump($toUpper); 坑 这个函数的使用,是有个坑的,这个坑就是,当转换之后,如果结果中有两个相同的 key,那么就会保留最后的那个。举个例子。 $arr = [ ‘key’ => 1, ‘kEy’ => 2, ‘keY’ => 3, ]; $toLower = array_change_key_case($arr, CASE_UPPER); var_dump($toLower); // [‘key’ => 3] 在这个例子中,我们发现,当执行转换之后,三个 key 变成相同的了,那么在这种情况下,只会保留最后一个元素作为 key。这里得到的数组是 [‘key’ => 3]。
Read more →

浅入理解 PHP 中的 Generator

何为 Generator 从 PHP 5.5 开始,PHP 加入了一个新的特性,那就是 Generator,中文译为生成器。生成器可以简单地用来实现对象的迭代,让我们先从官方的一个小例子说起。 xrange 在 PHP 中,我们都知道,有一个函数叫做 range,用来生成一个等差数列的数组,然后我们可以用这个数组进行 foreach 的迭代。具体就想这样。 foreach (range(1, 100, 2) as $num) { echo "{$num}\n"; } 这一段代码就会输出首项为 1,末项为 100,公差为 2 的等差数列。它的执行顺序是这样的。首先,range(1, 100, 2) 会生成一个数组,里面存了上面那样的一个等差数列,之后在 foreach 中对这个数组进行迭代。 那么,这样就会出现一个问题,如果我要生成 100 万个数字呢?那我们就要占用上百兆内存。虽然现在内存很便宜,但是我们也不能这么浪费内存嘛。那么这时,我们的生成器就可以排上用场了。考虑下面的代码。 function xrange($start, $limit, $step = 1) { while ($start <= $limit) { yield $start; $start += $step; } } foreach (xrange(1, 100, 2) as $num) { echo "{$num}\n"; } 这段代码所的出来的结果,和前面的那段代码一模一样,但是,它内部的原理是天翻地覆了。
Read more →

一个 PHPer 第一次用 Koa2 写 Node.js 的心路历程

学了一段时间的 js 了,突然想实践一下。正好公司有个小的项目要做,就顺手拿 Koa2 来做了。真是不做不知道,做了想不到。踩了一堆新手坑。 初次接触 Koa2 在知道 Koa2 之前,我也了解过 Express,可惜并没有实战用过。后来大家都说 Koa 是一个比 Express 更牛X的东西,于是在好(作)奇(死)心作祟下,直接去用 Koa2 了。后来证明的确是作死,原本用 PHP 一天就能写完东西,愣是让我搞了三天。 安装 最近 Node.js V8 发布了,原生支持 async 和 await 调用了,所以直接把 Node.js 升级了一下。 根据 Koa2 的教程,安装很简单,我是使用的 yarn 的(还真是比 npm 快)。 yarn add koa 默认就装了 Koa 2.2。然后装完了,其实我是一脸懵逼的,文档上说这样用。 const Koa = require(‘koa’) const app = new Koa() // response app.use(ctx => { ctx.body = ‘Hello Koa’ }) app.listen(3000) 我照着代码写了下来,的确成功了。可是,难不成我要把所有的逻辑写在 app.use 里? 中间件 我感觉我受到了惊吓,吓得我赶紧往下看文档。原来 Koa2 是一个中间件模型。app.
Read more →

理解 Go 语言中的方法和接收者

0x01 前言 Go 语言的语法实在有些不一样,与其它面向对象语言相比,Go 的方法似乎有些晦涩。 0x02 方法的定义 在 Go 语言里,方法和函数只差了一个,那就是方法在 func 和标识符之间多了一个参数。 type user struct { name string, email string, } //这是函数的定义 func notify(email string) { fmt.Println("Email is %s", email) } //这是方法的定义 func (u user) notify(email string) { fmt.Println("Email is %d", email) } 我们可以看到,方法是在 func 和 notify 之间多了一个 user 类型的参数 u,这个 u 就称作接收者。 0x03 接收者 接收者有两种,一种是值接收者,一种是指针接收者。顾名思义,值接收者,是接收者的类型是一个值,是一个副本,方法内部无法对其真正的接收者做更改;指针接收者,接收者的类型是一个指针,是接收者的引用,对这个引用的修改之间影响真正的接收者。像上面一样定义方法,将 user 改成 *user 就是指针接收者。 接收者与对象 相信有很多人看到这个接收者之后都很苦恼,到底这个接收者是什么,是干什么用的。我们在学习一门新的语言的时候,都讲究触类旁通,和我们已经了解的语言作对比。那么我们就通过拿 Go 和其它带有类的面向对象的语言做对比来搞清楚接收者是什么。这里我们用 php 来举例子。 在 php 中,我们要定义一个方法,首先是要定义一个类。
Read more →