用 github 可以做到類似遠端備份的功能,類似 svn 的中央 server(但不一樣)。每個 client 在本地端都有完整的 git repository,在 local 就可以做 commit 等操作,而非一定要連到 github。

先到 Github 申請帳號。github 提供免費帳號無上限的 public repository,如果要私人的 Repository 就要付錢囉。

以下操作都是在 Linux 上,在 Windows 上沒特別研究。

安裝 git

看是在哪種 Linux distribution 上,通常可以直接裝套件(Fedora 用 yum,Ubuntu 用 apt,Gentoo 用 emerge,可參考這裡)。

設定 ssh key

為了方便跟 github 傳輸資料,要設 ssh 的 public/private key:

1
$ ssh-keygen -t rsa

產生一組 key,預設放在 ~/.ssh/

Enter passphrase 是每次存取這把 key 時要輸入的密碼,有輸入的話每次用 git 指令 access github 都要打一次密碼。

到 github Account Setting 中的 SSH KEYS 增加 key,把剛產生的 public key(預設是 ~/.ssh/id_rsa.pub)貼上去。

在 github 新增 repository

照著網頁按,幾乎只要輸入 repository name 就可以了。

將現有的 git repository 上傳(push)至 github

1
2
3
$ cd mygit
$ git remote add origin git@github.com:[userid]/[rep's name].git
$ git push origin master

[userid] 是 github 上的帳號。[rep’s name] 是 repository name。

下載(clone)github 上的 repository 到 local 端

初次下載,在 Git 稱為 clone。

抓自己或其他人的皆可。

1
2
$ git clone git@github.com:[userid]/[rep's name].git [local dir name]
$ git clone https://github.com/[userid]/[rep's name].git [local dir name]

使用兩種不同 protocol clone。

將自己的修改傳上 github

1
$ git push

將 github 上較新的 code 抓下來

1
$ git pull

Ref

繼承分為 interface 繼承與 implement 繼承。interface 繼承僅繼承 interface,implementation 部分則由繼承者決定。implement 繼承則連同實作一同繼承。

C++ 語意上用 virtual 區分 base class 希望 derived class 自己定義與不希望 derived class 修改的 function,virtual 為希望 derived class 自行定義,non-virtual 則不希望 derived class 修改。

C++ 在區分 interface 及 implement 繼承上有:

  • pure virtual function
  • impure virtual function
  • non-virtual function

C++ virtual and inheritance

pure virtual function

pure virtual function 是為了讓 derived class 繼承 base class 的 interface,而 implementation 則由 derived class 全權處理。當一 class 宣告 pure virtual function 時,代表要求 derived class 必須 implement 這些 pure virtual function(沒寫會 compile error),通常是這個繼承體系所需要的共同性質。

impure virtual function

impure virtual function 是為了讓 derived class 繼承 interface 及預設的 implementation。也就是說,derived class 可以自己實作,也可以直接用 base class 的實作。

這有個風險是,因為在 derived class 中並不強迫要寫出 base class 的 impure virtual function,導致在 derived class 應該有自己實作的狀況下,programmer 可能忘記在 derived class 實作。

non-virtual function

non-virtual function 主要是為了讓 derived class 繼承 base class 的實作。

雖然語法上 derived class 可 override base class 的 non-virtual function,但語意上的設計不建議 derived class override base class 的 non-virtual function。另外,override virtual function 跟 override non-virtual function 在實際執行上會有不同行為。

non-virtua function 是 static binding。在一個繼承體系中 call non-virtual function 時會直接 call 宣告 type 的 class function。如果 derived class 沒有 override function,則因繼承關係 call 到 base class 的 function。如果 dervied class override function,則會 call 到 derived class 的。

Ref

  • 《Effective C++》Differentiate between inheritance of interface and inheritance of implementation.

應避免在 constructor 跟 destructor 中 call virtual function,否則容易造成跟預期不符的行為(誤以為可以 call 到 derived class 的 virtual function)。

construct 到 base class 時,C++ 視該 object 為 base class object,derived class 的 virtual function 跟 member 都還沒建出來,所以 call 不到 derived class 的 virtual function。

反過來的 destruct 則是在 derived class destructor 跑完後,derived class 的東西都刪光光了,該 object 就被當作 base class object,所以在 base class destructor 裡 call virtual function 不會 call 到 derived class 的 virtual function。

除了要避免 constructor 跟 destructor call 到 virtual function,也要避免它們 call 的 function 再間接 call 到 virtual function。

Ref

不能修改 class non-static member data 也不能 call 其他非 const member function 的 member function。

宣告 function 時在最後面加個 const 的 function,ex:

1
int GetIndex() const;

Declaring a member function with the const keyword specifies that the function is a “read-only” function that does not modify the object for which it is called. A constant member function cannot modify any non-static data members or call any member functions that aren’t constant.

compiler 會檢查 const member function 是否有改到 member data、return value 是否為 pointer 或 reference,如果改到 member data 或傳回可以讓外界修改 member data 時 compiler 會 error。可以透過這個特性反向檢查一個原本非 const 的 member function 是否有修改到 member data。

如果要在 const member function 中修改特定 member data,可以在該 member data 的宣告前面加上 mutable。

Ref

Read-Write lock 分成用於 read 跟 write 兩種 lock。使用 read lock 表示只會對 resource 做 read-only 的 access,使用 write lock 表示會修改 resource。

當一個 thread 要做 write 時,其他 read-only thread 會被 block 直到 write 做完。

Read-Write lock 適合有較多 read,write 較少但 priority 比較高的時候。

Qt 跟 Read-Write lock 相關的 class:QReadWriteLock、QReadLocker、QWriteLocker

Process Termination

Linux 中 process 結束時系統會回收該 process 使用的大部分 resource,僅留下結束資訊供它的 parent 取得。parent 回收 child process、取得這些資訊後,剩餘的 resource 才會被釋放。如果 parent 沒有回收結束的 child process,child 會變殭屍(zombie),即執行結束了但沒有把 resource 釋放乾淨,會佔用 PID 及一些記憶體空間放 exit status。

parent process 透過 call wait() 回收 child process 並取得 child process 的 exit status。

process 結束分成正常結束(terminate normally)跟不正常結束(terminate abnormally or crash)。

以系統的角度來看,process call exit() 或從 main() return 算 terminate normally。(實際上從 main() return 後就會 call exit() 系列 function。)process crash、被 signal 結束或其他狀況算 terminate abnormally。

以程式或 programmer 的角度來看,exit() 的參數為非 0 通常代表有些檢查沒有通過,程式無法繼續完成工作才會在中間 call exit()。這個狀況是「程式正常結束,但沒有完成該有的運作」,屬於程式執行邏輯上的錯誤,跟 crash 不同。

Process Exit Status

Linux 中以 exit status code 表示 process 結束時的狀態(它怎麼結束的)。

System

系統 exit status code 的範圍為 0 至 255。若程式設置超過 255 的 code,會以 mod 256 作為 exit code。

有些 code 已保留給系統使用、有固定意義。一般不建議 programmer 再對這些 code 定義其他意義。系統已保留的 exit code 為:0、1、2、126 ~ 165、255。

C/C++ program

在 C/C++ program 中 exit() 的參數值是在 process 正常結束下,讓 programmer 得知「程式階層上」的 exit status,跟系統所使用的 exit status code 相關但不相等

Read more »

PHP 的 Unit Test Framework。

Whenever you are tempted to type something into a print statement or a debugger expression, write it as a test instead.
~ Martin Fowler

test 要是 independent。一個 test 的 result 不能 depend 其他 test 的 result。

Install on Ubuntu 12.04

1
2
3
4
5
6
7
8
9
10
$ sudo apt-get remove phpunit    // 應該是前面有先用 apt-get install 裝了才需要
$ sudo pear upgrade pear // pear 只要裝了 php 就有
$ sudo pear channel-discover pear.phpunit.de
$ sudo pear channel-discover pear.symfony-project.com
$ sudo pear channel-discover components.ez.no
$ sudo pear update-channels
$ sudo pear upgrade-all
$ sudo pear install -alldeps phpunit/PHPUnit
$ sudo pear uninstall phpunit/PHPUnit
$ sudo pear install phpunit/PHPUnit

最後兩個指令看起來很好笑,好像是 pear 的 script 有問題。用倒數第三行裝完之後,又會說找不到 PHP_CodeCoverage_Filter::getInstance(),後來做這兩行就動了。

失敗的安裝

不能直接用

1
$ sudo apt-get install phpunit

不然 run 的時候會該找不到 PHP Code Coverage。

Run test

run all tests

1
2
$ phpunit
$ phpunit --stderr

PHPUnit 在一些系統上會有 header already sent 的錯誤,加 --stderr 可以避免 PHPUnit 把這視為測試錯誤。

執行單一測試,指定某測試檔做測試。

1
$ phpunit --stderr <test file path>

Basic conventions

  1. class “Class” 的 test 放在 class “ClassTest”
  2. ClassTest 要繼承 PHPUnit_Framework_TestCase
  3. test 為 public method,method name 為 test*
  4. 在 test 中,用 assert*() function check value 是否符合預期

failure vs error

failure 是 assertion function fail(要測試的 function 的 output 不符合預期)。以 F 代表。

error 是出現 unexpected exception 或 PHP error。以 E 代表。

setUp() & tearDown()

為了讓測試是正確且獨立的,要保持執行 test 時環境是乾淨的。在每次執行一個 test method 前會 call setUp() 建立 test 所需要的 object、變數等等。run 完 test method 後會 call tearDown() 清東西。

通常只需 implement tearDown(),用來 close file、socket 等。

用 file system 組合 test suite

可以用 file system 的 directory structure 達到組合出一個 test suite 的目的。這也是最簡單的方式。:)

建立一 test dir 並建立跟 project dir 中一樣的 directory structure。把 test case source file(就是 test method 的 source code)以相應 project source file 的位置放進去。PHPUnit 會自動 traverse test dir 做 test。

舉例說明:

這是 project directory:

1
2
object/
MyStack.php

這是 test directory:

1
2
tests/
MyStackTest.php

PHPUnit 會在 test dir(上例的 tests/)底下找 *Test.php 來做測試。

執行 test suite

1
$ phpunit --stderr <test dir path>

寫 test 就是…

  1. 生一些 testcase(input 及預期的 output)
  2. 在 test method 裡 call 要測的 function,把 test case 的 input 當作 arg 丟進去。
  3. 用 asseert function 判斷 function 的 output 是否符合預期。

Ref

man page 對 condition variable 的說明:

A condition (short for ``condition variable’’) is a synchronization device that allows threads to suspend execution and relinquish the processors until some predicate on shared data is satisfied. The basic operations on conditions are: signal the condition (when the predicate becomes true), and wait for the condition, suspending the thread execution until another thread signals the condition.

讓某些 thread suspend 起來,等某個條件成立才繼續往下執行。而另外有些 thread 則會更動條件中變數的值,並在該條件成立時 “signal condition” 叫起在 wait 的 thread 來執行。

A condition variable must always be associated with a mutex, to avoid the race condition where a thread prepares to wait on a condition variable and another thread signals the condition just before the first thread actually waits on it.

condition variable 必須搭配 mutex,避免 race condition:thread A 在 thread B signal condition 後才 wait 這個 condition variable,這種狀況可能讓 thread A 永遠在 sleep(如果沒有人會再 signal 這個 condition variable)。

如果所有 thread 在 signal condition variable 之前都 lock mutex,就能保證 condition 不會在 thread lock mutex 跟 wait condition variable 之間被 signal。或者說是不會在 check 過 condition 到 call wait function 以前其他 thread 就 signal condition variable。

pthread_cond_signal() 會叫起一個正在 wait condition variable 的 thread 來 run。如果沒有 thread 正在 wait,什麼事都不會發生。

pthread_cond_wait() 會 atomically unlcok mutex 並讓 calling thread wait condition variable(suspend 此 thread,suspend 的 thread 不會使用任何 CPU time)。這兩個動作是 atomically 執行的,不會被打斷。

進入 pthread_cond_wait() 前必須先 lock mutex。而 pthread_cond_wait() 在 return(可以 return 是因為有人 signal condition variable)前,會重新 lock 住 mutex。

mutex 用來保護多個 thread 共享(shared)的資料,避免 concurrent modification 造成問題。

lock mutex => modify shared data => unlock mutex

mutex 有三種 type:

  1. fast
  2. recursive
  3. error check

操作 mutex 的 function 會依照 type 不同而可能有不同的操作方式。

Initialize Mutex

initial 時,可以用 pthread_mutex_init() 或直接 assign const 指定 mutex 的 type。

1
2
3
4
5
6
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);

// 直接 assign const
pthread_mutex_t fastmutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t recmutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
pthread_mutex_t errchkmutex = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;

Lock Mutex

1
pthread_mutex_lock()

如果 mutex 是 unlock 狀態,則 lock 該 mutex 且讓該 mutex 為 call phread_mutex_lock() 的 thread 擁有,做完這些後立刻 return。

如果 mutex 正被 lock 住,此 function 會依照不同 type 的 mutex 有不同動作:

  1. fast

    calling thread 會 suspend,直到 mutex unlock。沒寫好的話可能造成 deadlock。

  2. recursive

    成功並立刻 return。這種 mutex 會記錄它被 lock 幾次,也就是一個 thread 可以 lock 這種 mutex 很多次。要 unlock 這種 mutex 時,也必須 call 相應 lock 次數的 unlock function 才能真正 unlock。

  3. error check

    立刻 return,但 errno = EDEADLK。真是個會讓人誤會的 errno 名稱…

    EDEADLK 在 man page 的解釋:

    the mutex is already locked by the calling thread (``error checking’’ mutexes only).

Try Lock Mutex

1
pthread_mutex_trylock()

跟 pthread_mutex_lock() 差不多,差別是這個不會 block 住。

操作 fast mutex 時,trylock() 會立刻 return,而 errno 為 EBUSY。

Unlock Mutex

1
pthread_mutex_unlock()

進入此 function 前,mutex 應為 locked 且被 calling thread 擁有。

fast mutex 會直接被 unlock。recursive mutex 需 call 相同於 lock 次數的 unlock 才會真正 unlock。

error check 跟 recursive 的 mutex 會檢查兩個條件:此 mutex 是否為 locked 且被 calling thread 擁有。若有一條件不符合,則不會 unlock,會 return error code。fast mutex 不會做此檢查,所以非擁有此 mutex 的 thread 也可以 unlock 它(這當然不好)。

Ref

  • Linux pthread_mutex man page
    • PS:Ubuntu 上要裝 glic-doc 才有 pthread 的 man page

為有「多型」性質(即有 virtual function)的 base class 宣告 virtual destructor。

C++ 沒有定義 base class 的 destructor 是 non-virtual 時以 base class pointer 刪除 derived class 的 object 的行為。常出現的結果是只有 base class 部分的 memory 會被釋放、derived class 部分的不會釋放,這會造成「局部銷毀」物件的問題,memory 當然就沒 free 乾淨。

用 virtual destructor 可以解決上述問題,因為 virtual function 的性質會 call 到 derived class 的 destructor。

不過不是把所有 base class 的 destructor 宣告成 virtual 就好,非必要(沒有 virtual function)時不需要這麼宣告。

Ref

  • 《Effective C++》