0%

在整理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所規定的。

看慣了x86的Intel Syntax,對於AT&T的語法還是不習慣。
使用以下方式可以將gcc編譯出來的Code變成Intel Syntax。

1
$ gcc test.c -S -masm=intel

這只支援gcc,clang不行。

突然想到,順手把他記下來吧。
在C++當中,如果要自己定義++和–這兩個operator,需要不同的Function signature。

1
2
3
4
5
6
7
8
class Date {
//...
public:
Date& operator++(); //prefix
Date& operator--(); //prefix
Date& operator++(int unused); //postfix
Date& operator--(int unused); //postfix
};

最大的差別就在於Postfix裡面有個未使用的參數。至於為什麼要這麼設計,可以參考這裡

這種技巧很常在Open Source的專案中看到,看了Arrays of Length Zero原本以為是GCC獨有Extensionm,後來發現Clang跟VC都能使用,記錄一下。

1
2
3
4
5
6
7
8
9
10
11
12
struct datapacket
{
int size;
int data[0];
];
void f()
{
struct datapacket *pkt =
malloc( sizeof(struct datapacket) +
sizeof(int)*9 );
/* further code . . . */
}

這邊的data[0]不佔記憶體空間,因此sizeof(struct datapacket)大小相當於sizeof(int),而data就是一個記憶體指標,指向一塊連續的記憶體。

一直想寫一篇關於LLVM的文章,想了好久終於下筆了。 同樣從Hello world開始下手

1
2
3
4
5
6
#include <stdio.h>
int main()
{
puts("Hello world!\n");
return 0;
}

編譯成 LLVM的IR中間形式,利用lli來執行IR Code。

1
2
$ clang hello.c -S -emit-llvm -o hello.lli
$ lli hello.lli

刪除hello.lli的Meta data之後,一個Hello world的最小部分掌這個樣子

1
2
3
4
5
6
7
8
@.str = private unnamed_addr constant [14 x i8] c"Hello world!\0A\00", align 1
define i32 @main() #0 {
%1 = alloca i32, align 4
store i32 0, i32* %1
%2 = call i32 @puts(i8* getelementptr inbounds ([14 x i8]* @.str, i32 0, i32 0))
ret i32 0
}
declare i32 @puts(i8*) #1