残阳似血的博客

Lost good things...

Django开发支持多帐号登录

之前曾经写过一篇文章,讲在Django开发中如何整合新浪微博API。当时,新浪微博只支持OAuth1.0,现在已经支持2.0版本,OAuth2.0协议进行了简化,且access token将不能永久使用,它存在一个过期时间。本文讲解了如何在你的django站点中支持多帐号登录,主要包括Google、新浪微博、人人和腾讯微博帐号,其实就是这个博客目前所支持的第三方帐号登录。

在这些第三方帐号中,Google、新浪微博以及人人都已经支持了OAuth2.0,而腾讯微博仍然停留在1.0阶段。

对于OAuth2.0,以Google帐号为例(Google也支持OpenID方式,读者可以自己去实现)。

继续阅读整篇文章 »

Go语言性能测试

大概在一年前,看到一个作者写了两篇文章,使用计算密集型的例子来比较了各个语言的性能(C# vs C++ 全局照明渲染性能比试C++/C#/F#/Java/JS/Lua/Python/Ruby渲染比试)。由于最近在使用Go,且想了解Go语言的性能,于是想起来这篇文章,并把文章中所提到的C++代码移植到了Go语言上。通过对几种语言实现的结果的比试,来了解Go目前的性能。

大概说一下作者所做的事。作者使用蒙地卡罗路径追踪来进行全局光照渲染。效果大致如下:

这个图片是512×512像素,每个像素点10000个采样。在实验中,考虑执行时间问题,只生成了256×256像素图片,每个像素点默认100次采样。

移植后的Go代码在这里。在基本上直译了C++代码之后,由于每个像素点之间是计算无关的,所以,我使用了goroutine来对每个像素点进行计算,以利用多核的计算能力。

本人的实验环境是Visual studio2010/.net framwork 4.0编译,Intel core2 P7570(2.26GHz主频)。VC++编译器选项参照了原作者,Go使用8g编译。结果如下:

语言 运行时长(秒) 比例
VC++(基准) 56.269 1.00x
C# 215.545 3.83x
JsChrome 247.231 4.39x
GO 325.000 5.78x
GO(goroutine) 172.000 3.06x

参照原作者,C++用OpenMP库可以取得极大提升,本文没有测试。可以看到,Go在单线程情况下性能还不尽如人意,在利用双核基础上,性能相对单线程有了接近一倍的提升。考虑到go进程调度和上下文切换的原因,goroutine的效果还是很不错的。

考虑到Go发布到现在才两年的时间,有着目前的性能还是可圈可点的。但是,Go语言还有很长的路要走,在编程范式相对稳定之后,我相信Go在性能方面会有较大优化。期待明年年初GO 1的正式发布!

继续阅读整篇文章 »

从生产者消费者问题看GO的并发机制

Go语言诞生已经两周年了。最近由于参加ECUG的缘故,我学习了一下Go语言的语法,就立刻被Go语言的特性所吸引。

Go语言一些背景就不介绍了,大家可以自行到Go主页进行查看(可能要准备梯子,和python.org一样悲催的命运)。Go的优点很多,最重要的特点之一是其第一个在语言层面对并发进行了支持。正如Go设计的主旨之一,就是现在的语言都没有对多核进行优化,对并发支持很不好。

Java,C#,Python这些语言对多线程编程有一样的模型。本文就生产者消费者问题对Java和Go进行对比。

生产者消费者模型大家应该很熟了,在操作系统的一些概念中经常提到,简单地说,就是生产者生产一些数据,然后放到buffer中,同时消费者从buffer中来取这些数据。这样就让生产消费变成了异步的两个过程。当buffer中没有数据时,消费者就进入等待过程;而当buffer中数据已满时,生产者则需要等待buffer中数据被取出后再写入。

Go语言中提出了channel的概念,它实际上就是一个管道,也可以理解为消息队列。Go线程(这里要注意,Go线程不同意一般的系统线程,而是类似于协程的概念,Go中称为goroutine)之间可以通过channel来进行通信,而channel本身是同步的。Go当中不鼓励通过加锁的方式来进行共享内存操作,而是鼓励通过管道通信来共享内存。我们来看具体的实现就明白了。

首先给出传统的Java中实现生产者消费者问题的代码。我们先写一个Mixin类,来进行对buffer中数据的取出和放入的操作。

public class Mixin {
	private int BUFFERSIZE = 0;
	private int[] Buffer;
	private int index = -1;
	
	public Mixin(int BufferSize) {
		this.BUFFERSIZE = BufferSize;
		this.Buffer = new int[this.BUFFERSIZE];
	}
	
	public synchronized void put(int item) {
		while(this.index >= this.BUFFERSIZE - 1) {
			try{
				wait();
			}
			catch(InterruptedException e) {
			}
		}
		this.Buffer[++this.index] = item;
		notifyAll();
	}
	
	public synchronized int get() {
		while(this.index < 0) {
			try{
				wait();
			}
			catch(InterruptedException e) { }
		}
		int result = this.Buffer[this.index--];
		notifyAll();
		return result;
	}
}

put和get操作都需要加上synchronized关键字来保证同步,代码很好理解就不讲了。接着,我们分别实现Producer和Consumer类,它们都继承了Thread类,并实现了run方法。

Producer类:

public class Producer extends Thread {
	private Mixin m = null;
	private int thisId;
	
	public Producer(Mixin m, int id) {
		this.m = m;
		this.thisId = id;
	}
	
	public synchronized void run() {
		for(int i=0; i<10; i++) {
			this.m.put(i);
			System.out.println("Producer " + this.thisId + " produces data: " + i);
			try {
				sleep(10);
			} catch (InterruptedException e) { }
		}
	}
}

Consumer类:

public class Consumer extends Thread {
	private Mixin m = null;
	private int thisId;
	
	public Consumer(Mixin m, int id) {
		this.m = m;
		this.thisId = id;
	}
	
	public synchronized void run() {
		for(int i=0; i<20; i++) {
			int item = this.m.get();
			System.out.println("Cosumer " + this.thisId + " get data: " + item);
			try {
				sleep(10);
			} catch (InterruptedException e) { }
		}
	}
}

Producer就是简单地把一些数放入buffer中。Java中通过wait方法来阻塞当前线程,而通过notifyAll来唤起所有阻塞的线程。最后我们写一个类来运行main函数,我们起了两个Producer线程和一个Cosumer线程。

public class ProducerConsumer {

	public static void main(String[] args) {
		Mixin m = new Mixin(6);
		Producer p1 = new Producer(m, 1);
		Producer p2 = new Producer(m, 2);
		Consumer c = new Consumer(m, 1);
		
		p1.start();
		p2.start();
		c.start();
	
	}

}

在Go中,这个问题变得异常简单。我们实现两个函数来分别表示Producer和Consumer,通过go关键字+函数名的方式,来启动goroutine。而由于channel的支持,我们可以在Producer和Consumer之间直接通过channel来进行通信。代码如下:

package main

import (
	"fmt"
	"time"
)

func Producer(id int, item chan int) {
	for i:=0; i<10; i++ {
		item <- i
		fmt.Printf("Producer %d produces data: %d\n", id, i)
		time.Sleep(10 * 1e6)
	}
}

func Consumer(id int, item chan int) {
	for i:=0; i<20; i++ {
		c_item := <-item
		fmt.Printf("Consumer %d get data: %d\n", id, c_item)
		time.Sleep(10 * 1e6)
	}
}

func main() {
	item := make(chan int, 6)
	go Producer(1, item)
	go Producer(2, item)
	go Consumer(1, item)
	
	time.Sleep(1 * 1e9)
}

channel的创建通过内建的make函数,第二个参数是channel通道的大小。通过item<-i,Producer把i放入item这个channel中,而Consumer通过<-item操作来从channel中取数据。当channel中的数据超过了其负荷值时,Producer这个goroutine就简单地阻塞,同样,当channel中没有数据时,Consumer也相应会阻塞。

可以看到,在语言级别上对并发的支持让Go中的多线程编程和异步编程变得很简单,也很易懂。

继续阅读整篇文章 »

Hadoop笔记之安装及伪分布式模式配置

Hadoop推荐的是Linux环境。Windows环境只能作为开发环境,而不能作为生产环境。

准备工作

首先确保Java环境的安装,并确保JAVA_HOME环境变量指向了一个Java安装。ubuntu下可以

sudo apt-get install sun-java6-jdk

通过运行“java -version”命令查看是否配置成功。

接着在这里下载一个稳定版的hadoop,目前最新的版本为0.20.203.x。

下载后,解压缩到本地文件系统。

tar xvzf hadoop-x.f.z.tar.gz

这时我们需要修改conf/hadoop-env.sh中的JAVA_HOME的值来指定Java安装。

创建一个指向hadoop安装目录(例如HADOOP_INSTALL)的环境变量。这样直接在命令行中运行hadoop命令就很方便。

export HADOOP_INSTALL=/home/chine/hadoop-0.20.203.0
export PATH=$PATH:$HADOOP_INSTALL/bin

为了能让每次开机添加环境变量,我们把这两句话加到"/etc/profile“的末尾。

我们运行以下指令看看hadoop是否安装成功。

% hadoop version

Hadoop 0.20.203.0
Subversion http://svn.apache.org/repos/asf/hadoop/common/branches/branch-0.20-security-203 -r 1099333
Compiled by oom on Wed May  4 07:57:50 PDT 2011

继续阅读整篇文章 »

正则表达式中的非获取匹配

正则表达式是代码中的常用技巧,主要用来匹配符合某种句法规则的字符串。常用的正则表达式本文不再赘述,详细可以参考wikiPythonJava)。本文主要讨论的是非获取匹配的正则表达式。

常见的非获取匹配有以下几种:

(?:pattern) 主要用来匹配pattern但不获取结果。我们看下面的例子。

>>> re.match("win (7|vista)", "win 7").group()
'win 7'

>>> re.match("win (7|vista)", "win 7").groups()
('7',)

>>> re.match("win (?:7|vista)", "win 7").group()
'win 7'

>>> re.match("win (?:7|vista)", "win 7").groups()
()

可以看出,使用这种模式括号中匹配内容并没有存储供以后使用。它是比“win 7|win vista”更加简便的形式。

以上是Python的示例,Java代码如下(以后的例子不再赘述):

Pattern ptn = Pattern.compile("win (7|vista)");
Matcher m = ptn.matcher("win 7");
while (m.find()) {
    for (int i = 0; i <= m.groupCount(); i++) {
        System.out.println(m.group(i));
    }
}

(?=pattern) 正向肯定预查。它在任何匹配pattern的字符串处查找字符串,它仅仅是检查是否匹配,而不将预查的结果包含其中。当然,它也是非获取匹配。看个例子就明白了。

>>> re.match("win (?=7|vista)", "win 7").group()
'win '

>>> re.match("win (?=7|vista)", "win 7").groups()
()

(?!pattern) 正向否定预查。例子如下:

>>> re.match("win (?!7|vista)", "win 8").group()
'win '

>>> re.match("win (?!7|vista)", "win 8").groups()
()

(?<=pattern) 反向肯定预查。查找某个字串之前是否满足pattern。

>>> re.search("(?<=95|98)win", "98win").group()
'win'

>>> re.search("(?<=95|98)win", "98win").groups()
()

(?<!pattern) 反向否定预查。

>>> re.search("(?<!95|98)win", "7win").group() 
'win'

>>> re.search("(?<!95|98)win", "7win").groups()
()

现在我们来看实际中的需求。现在我有一个字符串,这个字符串是由中文和英文组成,现在我要把这个字符串切成一个个只由中文或者英文组成的字串。比如说将“McCulloch与Pitts将神经系统”切成”McCulloch“、“与”、“ Pitts“、”将神经系统“。

首先要知道匹配中文的正则是”[\u4e00-\u9fa5]“,在Python中,我们可以使用findall方法:

reg = re.compile(u"[\u4e00-\u9fa5]+|[A-Za-z]+")
print "\t".join([w for w in reg.findall(u"McCulloch与Pitts将神经系统")])

对于Java,除了使用这种方式匹配,我们还使用刚刚说过的非获取匹配来达到同样的效果。对于需求的句子,可以看到要切分的地方,要么是中文接英文,要么是英文接中文。所以我们在反向肯定预查到中文然后正向肯定预查到英文处调用split操作,或者在反向肯定预查到英文正向肯定预查到中文处调用同样的操作。于是有:

Pattern ptn = Pattern.compile(
    "(?<=[\u4e00-\u9fa5])(?=[A-Za-z])|(?<=[A-Za-z])(?=[\u4e00-\u9fa5])", 
    Pattern.UNICODE_CASE);
String[] words = ptn.split("McCulloch与Pitts将神经系统");
for(String word: words) {
    System.out.print(word + "\t");
}

可以看到达到了一样的效果,但是,在我的测试中,此法对Python中的正则库中的split方法并不适用。所以我们解决问题确实需要灵活多变的策略。

最后,我们再讨论以下js的实现。对于js来说,由于js不支持反向预查。所以,用和Python方法类似的简单策略。我们使用加上“g“修饰符的match方法(表示全局匹配,而不是只匹配一次)。

var testStr = "McCulloch与Pitts将神经系统";
var regExp = /[\u4e00-\u9fa5]+|[A-Za-z]+/g;

testStr.match(regExp).forEach(function(i){
    document.write(i+"  ");
});

继续阅读整篇文章 »

联系信息

QQ: 344861256

E-mail: chinekingseu@gmail.com

更多信息 - 请访问 关于作者

获取更新

保持更新,通过订阅 RSS, 人人, 新浪微博 or Email