升级到ubuntu 12.04后Terminal(终端)乱码解决

执行如下命令,再重新打开Terminal即可,

sudo locale-gen zh_CN:UTF-8

另外,12.04后,左边的Launcher(启动栏)不再自动隐藏了,设置为自动隐藏的方法为:
System Settings => Appearance => Behavior => Auto-hide the Launcher (设置为on),同时将触发的灵敏度调高点,效果更好。

一读一写无锁队列

适用场景:
一生产者线程,一消费者线程,无锁高效的将数据从生产者线程传送至消费者线程,仅适用于一固定线程写入数据(push),另一固定线程读取数据(pop)
如:
网络游戏中RecvThread负责从socket中读取数据,传送至LogicThread中, LogicThread处理后,将需要发送的数据送至SendThread, 在LogicThread,SendThread上分别建立一个PipeQueue, 实现 RecvThread => LogicThread => WriterThread 无锁传送数据。
BTW:
1. 取名为PipeQueue的意义便限定此类如同pipe(管道), 只能一边写,另一边读。
2. __sync_synchronize() 很重要,如果去掉,g++ -O3 优化编译后的生成的程序会产生死锁。原因参考如下(具体原因我还不能够很详细的解释,待后续跟进此问题,关键词:memory barrier)

mfence指令 http://www.cnitblog.com/yuhensong/archive/2007/07/31/30994.aspx
__sync_synchronize() http://gcc.gnu.org/onlinedocs/gcc-4.6.2/gcc/Atomic-Builtins.html
GCC 提供的原子操作 http://www.cnblogs.com/FrankTan/archive/2010/12/11/1903377.html
memory barrier http://blog.sina.com.cn/s/blog_4b83fabd0100blhw.html
http://lxr.linux.no/#linux+v2.6.27.8/Documentation/memory-barriers.txt
windows下的memory barrier参考 http://msdn.microsoft.com/en-us/library/windows/desktop/ms684208%28v=vs.85%29.aspx
另外,来自msdn的关于memory barrier的解释比较清楚:
Creates a hardware memory barrier (fence) that prevents the CPU from re-ordering read and write operations. It may also prevent the compiler from re-ordering read and write operations.

3. item->data, item->next 操作的先后顺序是无锁的关键

#include <iostream>
#include <cassert>
#include <pthread.h>
 
using namespace std;
 
template<typename T>
class PipeQueue
{
public:
    PipeQueue() {
        head = new Item();
        head->next = NULL;
        tail = head;
    }
    ~PipeQueue() {
        assert(head->next == NULL);
        delete head;
    }
 
    void Push(const T& data) {
        Item* it = new Item();
        it->next = NULL;
 
        tail->data = data;
        __sync_synchronize(); // important
        tail->next = it;
        tail = it;
    }
 
    bool Pop(T& ret) {
        Item* tmp = NULL;
        __sync_synchronize(); // important
        if (head->next != NULL) {
            tmp = head;
            head = head->next;
            ret = tmp->data;
            delete tmp;
            return true;
        } else {
            return false;
        }
    }
 
private:
    struct Item {
        T data;
        Item* next;
    };
 
private:
    Item* head;
    Item* tail;
};
 
static void* thread_writer(void* param)
{
    PipeQueue<int>* queue = static_cast<PipeQueue<int>*>(param);
    for (int i = 0; i < 35000; i++) {
        queue->Push(i);
    }
 
    return NULL;
}
 
static void* thread_reader(void* param)
{
    PipeQueue<int>* queue = static_cast<PipeQueue<int>*>(param);
    int i = 0;
    int tmp;
    while (true) {
        if (queue->Pop(tmp)) {
            if (i != tmp) {
                cout<<"data error"<<endl;
                break;
            }
            i++;
            if (i > 30000) {
                break;
            }
        }
    }
    return NULL;
}
 
static void test1()
{
    PipeQueue<int> queue;
    queue.Push(1);
    queue.Push(2);
    int tmp;
    assert(queue.Pop(tmp));
    assert(tmp == 1);
    assert(queue.Pop(tmp));
    assert(tmp == 2);
    queue.Push(3);
    queue.Push(4);
    assert(queue.Pop(tmp));
    assert(tmp == 3);
    assert(queue.Pop(tmp));
    assert(tmp == 4);
    assert(!queue.Pop(tmp));
    assert(!queue.Pop(tmp));
    assert(!queue.Pop(tmp));
 
    cout<<" test1 completed... "<<endl;
}
 
static void test2()
{
    PipeQueue<int> queue;
    pthread_t writer, reader;
 
    pthread_create(&reader, NULL, thread_reader, &queue);
    pthread_create(&writer, NULL, thread_writer, &queue);
 
    pthread_join(reader, NULL);
    pthread_join(writer, NULL);
 
    // clean the queue
    int tmp;
    while (queue.Pop(tmp)) {
    }
 
    cout<<" test2 completed... "<<endl;
}
 
int main(int argc, char **argv)
{
    test1();
    test2();
    getchar();
    return 0;
}

用tr1::bind简化游戏世界驱动代码写法

目的:
World上的某些函数需要序列化执行(FIFO),有利于简化逻辑,去除锁需求

解决方案:
将此类需要序列执行的方法封装为Action,加入Action队列,依次调用

写法1:

代码如下,每在World上添加一个序列执行的方法时,即对的添加一个Action子类
缺点:代码较繁琐

#include <iostream>
#include <tr1/functional>
#include <queue>
 
using namespace std;
using namespace std::tr1;
 
class World
{
public:
    void Update() {
        Action* act = NULL;
        // lock m_acts
        if (!m_acts.empty()) {
            act = m_acts.front();
            m_acts.pop();
        }
        // unlock m_acts
        if (act != NULL) {
            act->Execute();
            delete act;
            act = NULL;
        }
    }
 
    void HandleMove(int x, int y) {
        m_acts.push(new ActionMove(this, x, y));
    }
 
private:
    class Action
    {
    public:
        Action(World* world) : _world(world) {
        }
        virtual void Execute() = 0;
    protected:
        World* _world;
    };
 
    class ActionMove : public Action
    {
    public:
        ActionMove(World* world, int x, int y) : Action(world), _x(x), _y(y) {
        }
    private:
        void Execute() {
            _world->Move(_x, _y);
        }
 
    private:
        int _x;
        int _y;
    };
 
    void Move(int x, int y) {
        cout<<"move to "<<x<<","<<y<<endl;
    }
 
private:
    queue<Action*> m_acts;
};
 
int main(int argc, char **argv)
{
    World world;
 
    // IO线程调用
    world.HandleMove(1, 1);
 
    // 驱动线程执行
    world.Update();
 
    getchar();
 
    return 0;
}

写法2:引用tr1::bind

#include <iostream>
#include <tr1/functional>
#include <queue>
 
using namespace std;
using namespace std::tr1;
 
class World
{
public:
    void Update() {
        Action* act = NULL;
        // lock m_acts
        if (!m_acts.empty()) {
            act = m_acts.front();
            m_acts.pop();
        }
        // unlock m_acts
        if (act != NULL) {
            (*act)();
            delete act;
            act = NULL;
        }
    }
 
    void HandleMove(int x, int y) {
        // lock m_acts
        m_acts.push(new Action(tr1::bind(&World::Move, this, x, y)));
        // unlock m_acts
    }
 
private:
    class Action {
    public:
        Action(tr1::function<void()> fun) : _fun(fun) {
        }
        void operator() () {
            _fun();
        }
    private:
        tr1::function<void()> _fun;
    };
 
    void Move(int x, int y) {
        cout<<"move to "<<x<<","<<y<<endl;
    }
 
private:
    queue<Action*> m_acts;
};
 
int main(int argc, char **argv)
{
    World world;
 
    // IO线程调用
    world.HandleMove(1, 1);
 
    // 驱动线程执行
    world.Update();
 
    getchar();
 
    return 0;
}

BTW:添加-std=c++0x时,可直接使用 #include<functional> 取代 #include<tr1/functional>

开发随想一

忘记在哪看到的这么一句话,随着年龄的增长,时间会越过越快,原因是十步的时候,每一年是生命的十分之一,而如果能活到八十岁的话,那么过一年只不过是生命的八十分之一而已。
要么就是我老得太快,要么就是拖延症太厉害,上一次写文章时下定决心,过“两几天做个总结”, 结果这几天就变得了六个月,在感叹时间的飞逝的同时,对自己的执行力感到汗颜。
非得给自己找个借口的话,那可能是因为在2011.10.21换了家公司,来了一家创业小公司,嗯,创业小公司的事总是很多的,工作也总是特别忙的,特别是前期的产品研发,在产品没上线的这段时间,研发部压力是最大的,喔,似乎现在整个公司基本都是研发部的人。
我呢,还是做服务器端的游戏开发,不过这次产品完全是从零开始,也算是一个不错的挑战,比起维护现有的代码,能亲手从零开始,设计,编码,实现一个游戏,还是比较爽的一件事。
目前产品做到差不多一半的样子,回顾这五个月来,感觉成长了不少,各方面的,遇到的问题也不少,也是各方面的。
成长一:关于新人培训
好吧,其它我也是个新人,IT行业总是需要不断的学习,知识更新,可能相对刚毕业的应届生来说,我也就多那么几年的经验吧。其实招聘的时候,我也没太纠结,有一定的基础知识,热爱编程,沟通能力强,勤奋就足够了,目前招到的人也还算基本满意。新人培训这块,我也没经验,我的想法只是套用我刚入行时,希望得到什么,然后从这方面去满足他们,尽自己的力量,帮助他们学到东西。至于企业文化之类,我只反对洗脑式的培训,工作一定是实现自我价值的同时,提升企业价值。
成长二:关于技术
好好的整理了erlang,C++,LUA方面的知识,不论哪种开发技术,经常用才能有感觉。这几个月来,特别是搭架构的初期,在解决某个问题的时候,经常陷于这样的考虑中,“这样是否合适,换种方式是否更好”,多考虑点总是好的,但考虑再多,也不及未来变化快。目前找到的解决方案就是,定时“重构”部份代码,以适应需求的变动,以及后期发现的新的问题。个人感觉,我有一定的“代码洁癖”,不能容忍代码太乱,逻辑不清,“重构”之后,看着代码变得清楚些了,心情也会突然变得很好,连晚上都能睡个好觉,不然的话,总是会觉得很忧虑。

问题一:关于进度
进度,这真是一个让程序员烦恼的词,呆了几家公司,似乎每一家开发公司都或多或小有开发进度延误的问题。我在想,如果只是单纯的写代码,不用关心进度表,直到自己觉得做得非常不错了,才交出去,应该蛮有乐趣的,不过却没有商业价值了。而如今,当自己需要来亲自把控”进度”这个东西的时候,不得不多想一些了。
延误进度的因素太多了,主要的有需求变动,工作量预估错误,致命BUG等,我想在对这方面没太多想法,只能想着提高开发效率,避免过早优化,尽量早出功能为好。
明天盘算着做一个TODOlist, 把所有工作细致的列出来,同时做一个DONElist,把每天完成的功能特性列出来,争取每天都有新的产品feature,也算是“持续交付”的一种体现形式吧。
问题二:关于身体
早上八点出门,晚上十点回家,没怎么见过白天的阳光,眼睛长时间都在屏幕前,在这样下去快要瞎掉啦。感觉近视明显加重了。

一年多时间就这么过了!

翻看上一次博客时间,已是2010年7月的事了。

一年多前,阴差阳错,换了份工作,WebGame开发,这段时间过来,感触颇多,过几天写个总结,重新开启博文之路。

marquee 在 IE8 中显示不完全的问题

在使用 marquee 作图片滚动时,当设定外部容器的宽度时,在IE8中往往内部图片并不能完全展示,有时候换行,有时候缺失,解决的办法如下:

1. 在HTML的头部添加如下 META

2. 使用 JS 代码来替换 marquee

推荐使用第二种,毕竟 marqee 并不属于标准的 XHTML 元素

ZendFramework的视图插件加载机制

如下的简单代码展示了Zend_View中的插件函数加载机制
就是巧妙应用PHP5的魔术方法__call, 当调用指定扩展函数时,自动进行扩展的实例化,即Zend_View_Helper_*之类

<?php
 
class View
{
    # 存储扩展助手对象
    protected $helper = array();
 
    # 魔术方法 __call 当调用方法不存在时,自动调用扩展中的方法
    public function __call($plugin, $args)
    {
        $this->addPlugin($plugin);
        return call_user_func_array(array($this->helper[$plugin], $plugin), $args);
    }
 
    public function addPlugin($plugin)
    {
        # 添加一个扩展,此处在ZendFramework中使用了规范命名与规范目录,方法自动查找
        if (!isset($this->helper[$plugin])) {
        	$class = 'Plugin_'.ucfirst($plugin);
            $this->helper[$plugin] = new $class;
        }
    }
}
 
class Plugin_Url
{
    public function url($href, $label)
    {
        return '<a href="' . $href . '">' . $label .'</a>';
    }
}
 
$view = new View();
echo $view->url('http://www.google.com.hk', 'GoogleInCN');

当在MySQL中使用float类型时,运行>=,=操作时遇到的问题

考查有如下的MySQL表

create table test(
id int auto_increment not null primary key,
balance float default 0);

增加三条新记录

insert into test(balance) values(0.01);
insert into test(balance) values(0.02);
insert into test(balance) values(0.03);

查询一下
mysql> select * from test;
+—-+———+
| id | balance |
+—-+———+
| 1 | 0.01 |
| 2 | 0.02 |
| 3 | 0.03 |
+—-+———+

问题来了:
mysql> select * from test where balance = 0.01;
Empty set (0.00 sec)

mysql> select * from test where balance >= 0.02;
+—-+———+
| id | balance |
+—-+———+
| 3 | 0.03 |
+—-+———+
1 row in set (0.00 sec)

mysql> select * from test where balance between 0.02 and 0.03;
+—-+———+
| id | balance |
+—-+———+
| 3 | 0.03 |
+—-+———+
1 row in set (0.00 sec)

mysql> select * from test where balance <= 0.02;
+----+---------+
| id | balance |
+----+---------+
| 1 | 0.01 |
| 2 | 0.02 |
+----+---------+
2 rows in set (0.00 sec)

通过如上一的系列操作,会发现 float类型,竟然不支持 = (等于),>= (大于等于),让人的疑惑的是 <=, between的后半部却是有效的。

查询了一下mysql document,找到如下解释:

If you are comparing FLOAT or DOUBLE columns with numbers that have decimals, you can't use equality (=) comparisons. This problem is common in most computer languages because not all floating-point values can be stored with exact precision. In some cases, changing the FLOAT to a DOUBLE fixes this. See Section B.5.5.8, “Problems with Floating-Point Values”.

Similar problems may be encountered when comparing DECIMAL values prior to MySQL 5.0.3.

看来 0.01 数据,mysql 默认做 double 类型进行处理。

解决方法:
正如官网上说的,将 float 类型改为 double 类型吧。

alter table test change balance balance double default 0;

改完之后的问题:

mysql> select * from test;
+—-+———————+
| id | balance |
+—-+———————+
| 1 | 0.00999999977648258 |
| 2 | 0.0199999995529652 |
| 3 | 0.0299999993294477 |
+—-+———————+
3 rows in set (0.00 sec)

数据变成如此长的精度,我只想保留两位,四舍五入一下吧。

update test set balance = round(balance, 2);

至此解决问题了,并且决定以后不再在MySQL中使用 FLOAT 类型了,至少要慎用!

CentOS上快速搭建Subversion[SVN]版本控制服务器

这里所说的快速搭建是指建立一个私人使用的SVN服务器,不带Web访问功能,并不需要配置与Apaceh相关联的东东。

原文地址:http://www.electrictoolbox.com/install-subversion-centos/

第一步:安装Subversion

yum install subversion

第二步:添加运行Subversion Server的用户

sudo useradd svn
sudo passwd svn
# 设置密码 ******

第三步:创建代码仓库(Repository)

# 切换至home
cd ~
mkdir repositories
# 切换至代码仓库
cd repositories

第四步:创建工程,配置工程

# 创建一个工程,名为 myproject
svnadmin create myproject
# 配置工程
vim myproject/conf/svnserve.conf
# 取消掉如下两行的注释
# auth-access = write
# password-db = passwd
 
# 配置该工作的用户与密码
vim myproject/conf/passwd
[users]
username = password

第五步:启动SVN Server

# 切换至home
svnserve -d -r /home/svn/repositories

现在,可以使用如各种SVN客户端进行版本控制了(例如windows上的 TortoiseSVN)
checkout的地址为 svn://yourhost/myproject
用户名和密码为在 myproject/conf/passwd 中所配置的内容

Memcached(PHP另一个Memcache客户端)的安装与使用

在PHP中使用Memcache进行数据缓存有两个客户端(pecl扩展)可供选择,一个名叫Memcache,另一个名叫MemcachedMemcached支持更多操作接口,内且内置了Memcache服务器池(集群)的管理,也许在面对多个Memcache服务器时有更好的效果。更多特性待尝试一下便知道了。

如下记录安装过程与使用方法:

由于Memcached是基于libmemcached开发的,所以应当先安装libmemcached

libmemcached的下载地址为:http://download.tangent.org/ 选择一个最新版的libmemcachedXXX.tar.gz即可

然后解包 ./configure && make && make install 即可

接下来直接用pecl install memcached 安装 memcached客户端,然后在php.ini中添加extension=memcached.so即可。

使用:(待。。)