0%

在網路上搜尋了一下,發現這功能真是好用。
原先的git submodule缺點不少,一堆批評的聲音。因此之後就有git subtree的誕生,,官方也建議用git subtree解決大部分的問題。

主要應用在兩個場景

引用另外一個Repository的管理

假設在我們的Repository當中,需要引用另一個Repository(如Application需要3rd party library)的內容時,盡其希望其Repository能夠跟著更新,一旦我們對其修改時,也能擁有修改和提交的權利。

第一步: 建立目前Repository跟Sub Repository的關聯性

1
2
$ git remote -f add <name of sub_repo> <address of sub_repo>
$ git subtree add --prefix=<name of sub_directory> <name of sub_repo> <branch> --squash

第一個指令加上-f是建立關聯之後再度進行fetch作業。
而第二個指令加上--squash是要把Sub Repository的history合併成一個。
例如

1
2
$ git remote add -f ai https://github.com/aoxu/ai.git  
$ git subtree add --prefix=ai ai master --squash

第二步: 更新Sub Directory

一旦Sub Repository有人更改之後,我們希望把修改的東西合併到我們 Repository中。

1
2
$ git fetch <name of sub_repo> <branch>
$ git subtree pull --prefix=<name of sub_directory> <name of sub_repo> <branch> --squash

這就類似上面的情況了,一樣給的範例

1
2
$ git fetch ai master  
$ git subtree pull --prefix=ai ai --squash

第三步: 將修改推送到Remote Repository

Push只需要一個步驟

1
$ git subtree push --prefix=<name of sub_directory> <name of sub_repo> <branch>

同樣有個範例

1
$ git subtree push --prefix=ai ai master  

如果不用git subtree的話,也有個Subtree merge strategy見仁見智了。

參考資料:

將Sub directory拆成令外一個Repository

這剛好跟上面那個使用奇境相反,使用方式如下

第一步: 先將sub directory的資料建立new branch

1
$ git subtree split -P <sub_directory> -b <branch>

第二步: 準備new repository並且啦取原先repository的branch資訊

1
2
3
$ mkdir <new-repo> && cd <new-repo>
$ git init
$git pull <path of original repo> <name-of-new-branch>

第三步: 建立Remote Repository的關聯並推送到遠方

1
2
$ git remote add origin <git@github.com:my-user/new-repo.git>
$ git push origin -u master

參考資料:

看了書之後豁然開朗啊,之前還真是個半調子。總結一下使用情境。

從失敗的例子講起

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct Obj {
string name_;
Obj(const string& name) :name(name_) {}
~Obj() {}
};
class ObjFactory {
map<string, shared_ptr<Obj>> lookup_;
public:
shared_ptr<Obj> get(const string &name)
{
auto it = lookup_.find(name);
if (it != lookup_.end())
return it->second;
lookup_[name] = shared_ptr<Obj>(new Obj(name));
return lookup_[name];
}
};

這段程式碼最大的問題就是,如果ObjFactory的instance沒被摧毀,所有拿到的Obj都不會被釋放。

用 weak_ptr 取代 shared_ptr

在ObjectFactory的部份不要保存shared_ptr,這樣會增加reference count,用weak_ptr取而代之。需要的話再promotion成shared_ptr。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class ObjFactory {
map<string, weak_ptr<Obj>> lookup_;
public:
shared_ptr<Obj> get(const string &name)
{
auto it = lookup_.find(name);
if (it != lookup_.end())
{
shared_ptr<Obj> obj((it->second).lock());
return obj;
}
shared_ptr<Obj> instance(new Obj(name));
weak_ptr<Obj> obj(instance);
lookup_[name] = obj;
return instance;
}
};

看起來沒什麼問題,不過譽到以下這種情況就爛了

1
2
3
4
5
6
7
8
9
10
for (int i = 0; i < 3; i++)
{
shared_ptr<Obj> s = pFactory->get("HM");
if (s) {
cout << "create new obj" << endl;
}
else {
cout << "cannot create new obj" << endl;
}
}

我們希望看到的是每次都能拿到一個新物件,結果發現只有第一次能成功。原因出在當Obj被摧毀的時候,沒有順便清理掉ObjFactory 當中的lookup_的資料,以致於下一次使用的時候,可以找到上一次殘留的屍體,promtion之後就是一個空的shared_ptr。
解決方法就是使用shared_ptr時,同時自訂一個destructor,除了釋放memory之外,也把map裡面的資料輕空。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class ObjFactory {
map<string, weak_ptr<Obj>> lookup_;
void deleteObj(Obj *pObj)
{
lookup_.erase(pObj->name_);
delete pObj;
}
public:
shared_ptr<Obj> get(const string &name)
{
auto it = lookup_.find(name);
if (it != lookup_.end())
{
shared_ptr<Obj> obj((it->second).lock());
return obj;
}
shared_ptr<Obj> instance(new Obj(name), bind(&ObjFactory::deleteObj, this, placeholders::_1));
weak_ptr<Obj> obj(instance);
lookup_[name] = obj;
return instance;
}
};

現在看起來正常了,不過街下來這個Case又會遇到問題。

1
2
3
4
{
shared_ptr<Obj> obj1 = pFactory->get("Obj1");
delete pFactory;
}

由於在這個block之內,pFactory已經被釋放了,所以那個destructor的this一點都不可靠,因此要把裡面那個this轉成一個shared_ptr。

enable_shared_from_this

enable_shared_from_this就是因此登場的,他可以把this指標所在的位置轉成一個shared_ptr。
而原先的測試部份也必須用shared_ptr管理了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class ObjFactory : public enable_shared_from_this<ObjFactory> {
map<string, weak_ptr<Obj>> lookup_;
void deleteObj(Obj *pObj)
{
lookup_.erase(pObj->name_);
delete pObj;
}
public:
shared_ptr<Obj> get(const string &name)
{
auto it = lookup_.find(name);
if (it != lookup_.end())
{
shared_ptr<Obj> obj((it->second).lock());
return obj;
}
shared_ptr<Obj> instance(new Obj(name), bind(&ObjFactory::deleteObj, shared_from_this(), placeholders::_1));
weak_ptr<Obj> obj(instance);
lookup_[name] = obj;
return instance;
}
};
{
shared_ptr<ObjFactory> pFactory(new ObjFactory);
shared_ptr<Obj> obj1 = pFactory->get("Obj1");
}

Misc

使用shared_ptr會延長ObjectFactory的LifeCycle,如果pFactory已經不在的話,跟本連清理的動作都不用作。
所以程式可以寫成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class ObjFactory : public enable_shared_from_this<ObjFactory> {
map<string, weak_ptr<Obj>> lookup_;
static void deleteObj(const weak_ptr<ObjFactory> &pWeakFactory, Obj *pObj)
{
shared_ptr<ObjFactory> pFactory(pWeakFactory.lock());
if (pFactory) {
pFactory->lookup_.erase(pObj->name_);
}
delete pObj;
}
public:
shared_ptr<Obj> get(const string &name)
{
auto it = lookup_.find(name);
if (it != lookup_.end())
{
shared_ptr<Obj> obj((it->second).lock());
return obj;
}
shared_ptr<Obj> instance(new Obj(name), bind(&ObjFactory::deleteObj,
weak_ptr<ObjFactory>(shared_from_this()), placeholders::_1));
weak_ptr<Obj> obj(instance);
lookup_[name] = obj;
return instance;
}
};

結論

有本好書真的很重要啊,以前我只會用最粗淺的shared_ptr,對於wear_ptr跟其他特性玩全部熟。有了範例之後至少有個基本認識。
至於程式寫得這麼複雜到令人髮指也是不太好,Garbage collection很大部份可以紓解Programmer的負擔。

其他語言已經有yield的語意了,不過C/C++必須手動模擬。
看了Coroutines in C之後,做了一下實驗。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int func()
{
static int i, state = 0;
switch (state)
{
case 0: goto LABEL0;
case 1: goto LABEL1;
}
LABEL0:
for (i = 0; i < 10; i++) {
state = 1;
return i;
LABEL1:;
}
return -1;
}

測試VC12、GCC和Clang之後,發現GCC要使用C99模式編譯才會成功。
不過美增加一個狀態就得增加一個LABEL,也是蠻麻煩的一件事,後來看到一個作法更好,之前從沒想過能這樣用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int func()
{
static int i, state = 0;
switch (state)
{
case 0:
for (i = 0; i < 10; i++) {
state = 1;
return i;
case 1:;
}
}
return -1;
}

從沒想過switch/case的statement可以這樣用,開了眼界了。

在C++11中,可以這樣用已經不是什麼新鮮事了。

1
2
3
4
5
vector<int> vec;
for (int i : vec )
{
cout << i << endl;
}

如果要在自己的container支援這特性的話,需滿足以下條件。

  • Container必須擁有beginend函數,這兩個函數必須回傳一個 Iterator
  • Iterator必須擁有*++ (prefix版)!=這三個operator function。
    以下範例是從C++11 range-based for loops修改而來,加上自己的實驗。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    #include <iostream>
    #include <algorithm>
    using namespace std;
    class IntVector;

    class ConstIter
    {
    public:
    ConstIter(const IntVector* p_vec, int pos)
    : _pos(pos)
    , _p_vec(p_vec)
    { }
    bool operator!= (const ConstIter& other) const
    {
    return _pos != other._pos;
    }
    int operator* () const;
    const ConstIter& operator++ ()
    {
    ++_pos;
    return *this;
    }
    private:
    int _pos;
    const IntVector *_p_vec;
    };

    class Iter
    {
    public:
    Iter(IntVector* p_vec, int pos) : _pos(pos) , _p_vec(p_vec) { }
    bool operator!= (const Iter& other) const
    {
    return _pos != other._pos;
    }
    int& operator* ();
    Iter& operator++ ()
    {
    ++_pos;
    return *this;
    }
    private:
    int _pos;
    IntVector *_p_vec;
    };

    class IntVector
    {
    public:
    int get(int col) const { return _data[col]; }
    int& get(int col) { return _data[col]; }

    IntVector() {}



    Iter begin()
    {
    return Iter(this, 0);
    }

    Iter end()
    {
    return Iter(this, 100);
    }

    ConstIter begin() const
    {
    return ConstIter(this, 0);
    }
    ConstIter end() const
    {
    return ConstIter(this, 100);
    }

    void set(int index, int val)
    {
    _data[index] = val;
    }

    private:
    int _data[100];
    };

    int
    ConstIter::operator* () const
    {
    return _p_vec->get(_pos);
    }

    int&
    Iter::operator* ()
    {
    return _p_vec->get(_pos);
    }

    int main()
    {
    IntVector v;
    for (int i = 0; i < 100; i++)
    v.set(i, i);
    transform(v.begin(), v.end(), v.begin(), [](int v) { return v * 2; });
    for (int& i : v) { i *= 2; }
    for (const int& i : v) { cout << i << endl; }
    }
    在gcc跟clang都能正常運作,不過到了VC12 Debug Mode就編譯失敗了。
    這是由於Checked Iterator這巷特性。
    最快的解決方案是在前面加上
    1
    2
    3
    4
    5
    6
    #ifndef _ITERATOR_DEBUG_LEVEL
    #define _ITERATOR_DEBUG_LEVEL 0
    #else
    #undef _ITERATOR_DEBUG_LEVEL
    #define _ITERATOR_DEBUG_LEVEL 0
    #endif

在整理Concurrency programming資料的時候,發現這個部份被我遺漏了,寫點東西免得忘記。
libdispatch 是由蘋果開發的Concurrency framework,如今也可以在FreeBSD上使用。

FreeBSD Wiki上找來的範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <dispatch/dispatch.h>

#include <err.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
dispatch_queue_t q;
dispatch_time_t t;

q = dispatch_get_main_queue();
t = dispatch_time(DISPATCH_TIME_NOW, 5LL * NSEC_PER_SEC);

// Print a message and exit after 5 seconds.
dispatch_after(t, q, ^{
printf("block_dispatch\n");
exit(0);
});

dispatch_main();
return (0);
}

看到那個 ^{ .... } 區塊的部份就類似於其他語言的Closure,C++11的lambda expression
至於要編譯這段程式碼,就需要

1
# clang -Wall -Werror -fblocks -L/usr/local/lib -I/usr/local/include -o test test.c -ldispatch

Blocks是Clang的Extension,更多資訊可以參考Programming with C Blocks,GCC不支援,至於libdispatch需要在ports下事先安裝。編譯的時候要記得加上-fblockss
當然,也可以有無Blocks的版本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <dispatch/dispatch.h>

#include <err.h>
#include <stdio.h>
#include <stdlib.h>

void
deferred_code(__unused void *arg)
{

printf("block_dispatch\n");
exit(0);
}

int main(int argc, char *argv[])
{
dispatch_queue_t q;
dispatch_time_t t;

q = dispatch_get_main_queue();
t = dispatch_time(DISPATCH_TIME_NOW, 5LL * NSEC_PER_SEC);

dispatch_after_f(t, q, NULL, deferred_code);

dispatch_main();
return (0);
}

編譯的時候就可以拿掉-fblocks

1
# clang -Wall -Werror -I/usr/local/include -L/usr/local/lib -o test2 test2.c -ldispatch

除了Cuncurrency之外,Closure的觀念也在很多程式語言開枝散葉了。

這個pdf有對libdispatch作個簡單的介紹。
在各語言下都有類似libdispatch這樣的Framework

雖然大部分時間都在Windows/Linux底下打轉,FreeBSD被我晾在一旁(時間不夠用Orz)
不過趁著FreeBSD 10.0 Release
把自己的9.1-Release升級到10.0-Releae,並把過程記錄下來。

1
2
3
4
5
6
7
# freebsd-update upgrade -r 10.0-RELEASE
# freebsd-update install -r 10.0-RELEASE
# reboot
# freebsd-update install
# reboot
# uname -ra
FreeBSD freebsd 10.0-RELEASE FreeBSD 10.0-RELEASE #0 r260789: Thu Jan 16 22:34:59 UTC 2014 root@snap.freebsd.org:/usr/obj/usr/src/sys/GENERIC amd64

安裝完10.0之後,發現gcc不存在於,全部用clang取代掉了。
另外就是使用 pkgng 來當做新的Package manager,用法就類似於 apt 或 yum。

1
2
3
4
5
6
7
8
9
# pkg update
# pkg search vim
vim-7.4.110_3
vim-lite-7.4.110
vimpager-1.8.3
xpi-vimperator-3.5
# pkg install vim-7.4.110_3
# pkg remove vim-7.4.110_3
# pkg autoremove

更多的使用可以參考pkgng: First look at FreeBSD’s new package manager

解決Concurrency中shared infomation的方法不只一總,同樣是Message-based syste,Channel不過跟Actor model不同

  • Actor是直接跟其他Actor作溝通
  • Channel是兩端透過一個Channel連接,然後透過Channel傳輸到另一端,類似Unix的pipe

「Go](http://golang.org/)是最著名使用Channelmodel當做標準配備的程式語言。
看看Go網站上提供的範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// A concurrent prime sieve

package main

import "fmt"

// Send the sequence 2, 3, 4, ... to channel 'ch'.
func Generate(ch chan<- int) {
for i := 2; ; i++ {
ch <- i // Send 'i' to channel 'ch'.
}
}

// Copy the values from channel 'in' to channel 'out',
// removing those divisible by 'prime'.
func Filter(in <-chan int, out chan<- int, prime int) {
for {
i := <-in // Receive value from 'in'.
if i%prime != 0 {
out <- i // Send 'i' to 'out'.
}
}
}

// The prime sieve: Daisy-chain Filter processes.
func main() {
ch := make(chan int) // Create a new channel.
go Generate(ch) // Launch Generate goroutine.
for i := 0; i < 10; i++ {
prime := <-ch
fmt.Println(prime)
ch1 := make(chan int)
go Filter(ch, ch1, prime)
ch = ch1
}
}

原先的ch 是 [2, 3, 4, 5, 6, ….]的Channnel
在從for-loop中拿出2之後,變成[3, 4, 5, 6, …]
再度經過Filter函數,將2的倍數濾掉之後, ch1就是[3, 5, 7, 9, ….]的Channel
之後將ch1複製到ch,重複以上的動作。

這邊有Scala跟Go對Cuncurrency的比較
這邊是Go cunnerency的投影片

About Actor Model

在Concurrency programming流行之後,Actor Model又重新熱門起來了。
一個Actor要做的就是以下幾件事情

  • 建立其他 Actor
  • 對其他Actor發送消息
  • 接收並處理消息
    因此,Erlang可以這樣寫
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    loop() ->
    receive
    {From, {store, Key, Value}} ->
    put(Key, {ok, Value}),
    From ! {kvs, true},
    loop();
    {From, {lookup, Key}} ->
    From ! {kvs, get(Key)},
    loop()
    end.
    而沒有支援Tail Recursion的程式語言,如Scala等,用Loop來模擬Tail Recursion。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    class Ping(count: int, pong: Actor) extends Actor {
    def act() {
    var pingsLeft = count - 1
    pong ! Ping
    while (true) {
    receive {
    case Pong =>
    if (pingsLeft % 1000 == 0)
    Console.println("Ping: pong")
    if (pingsLeft > 0) {
    pong ! Ping
    pingsLeft -= 1
    } else {
    Console.println("Ping: stop")
    pong ! Stop
    exit()
    }
    }
    }
    }
    }
    至於C++,有Theron這套Framework可以建構Actor,至於Event-based或是Thread-based就取決於用途了。

Why use Actor Model

根據網路上找到的資料,把它歸類為這幾個理由

  • No shared infomation,在多個Thread共享一段資料的時候,需要用Atomic operation或是Locks作保護。除了性能可能有所損失的話,不當的Lock sequence也會造成Dead lock。而Actor可以將information保存於單一個Actor當中。這樣就少去了錯誤同步的風險。 當然,消除shared infomation的方案還有Functional programming。
  • Asynchronous,雖然Scala有提供同步版本的API。不過大部分的Actor implementation都是Asynchronous。不必耗費CPU Resource在等待完成的時刻。

在C++中,new / delete 總共有三種用法。new跟delete的用法類似,所以就以new來示範。

operator new

詳細細節可以參考operator newoperator new, operator new[]
可以透過function overload訂製自己的new和delete動作。
operater new/delete就相對於C語言的malloc/free。因此透過operator new分配到的記憶體就該用operator delete釋放。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct Object {
Object() {std::cout << '*' << std::endl; } // print an asterisk for each construction
void* operator new(size_t size)
{
return ::operator new(size);
}
void operator delete(void *ptr)
{
return ::operator delete(ptr);
}
void* operator new[](size_t size)
{
return ::operator new[](size);
}
void operator delete[](void *ptr)
{
return ::operator delete[](ptr);
}
};
Object *obj = new Object;
delete obj;

這時就會使用Object裡面的new/delete的函式了。

Placement new

Placement new只是operator new的一種overload版本

1
2
3
4
inline void *operator new(size_t, void *_Where) 
{
return (_Where);
}

詳細使用可以參考C++ FAQ中的介紹。
我們可以從任何記憶體位置(在Heap或是Stack都行)進行new constructor的動作。

1
2
3
4
5
6
7
8
void *pMemory = malloc(sizeof(Object));
Object *pObj1 = new (pMemory) Object;
pObj1->~Object();
free(pMemory);

char stackObj[sizeof(Object)];
Object *pObj2 = new (stackObj) Object;
pObj2->~Object();

Placement new的存在是有意義的,可以自由控制Memory的取得方式,可以從Stack/Heap/Memory Pool中取得記憶體,對於某些情景之下有更大的

new operator

這大概是最常見的動作,基本上就是以下兩個步驟構成

  • 呼叫 operator new
  • 呼叫 Constructor
    這個順序是固定的,無法opverload,這是C++ Standard所規定的。