(1): linux command
Mount USB:
mount -t vfat /dev/sda1 /mnt/cdrom
Unmount USB:
umount /mnt/cdrom
copy directory recyle:
cp -r apache-ant-1.7.0 /usr/local
unpackage file:
tar -zxvf apache-ant-1.7.0-bin.tar.gz
ctr+alt+f4 swich to dos interface
alt+f7 return to graphic
find / -name filename
ping local ip : ifconfig
vi usage :
[esc] [insert]
:w write
:q quit
(2): software install
From the Ant FAQ:
How do you get ant-1.6.x (or any version later than 1.5.2) to work on on RedHat ES 3?
Redhat ES 3.0 comes installed with ant 1.5.2. Even if you have your PATH and ANT_HOME variables set correctly to a later version of ant, you will always be forced to use the preinstalled version.
To use a later version of ant on this OS you could do the following:
$ ant -version
Apache Ant version 1.5.2-23 compiled on November 12 2003
$ su -l
# rpm -e ant ant-libs
# exit
$ hash -r
$ ant -version
Apache Ant version 1.6.2 compiled on July 16 2004
But on my computer similar do like this:
[root@localhost TestAntBuld]# ant -version
Apache Ant version 1.5.2-20 compiled on September 25 2003
[root@localhost root]# sudo -l
User root may run the following commands on this host:
(ALL) ALL
[root@localhost root]# rpm -e ant ant-libs
[root@localhost root]# exit
logout
[root@localhost root]# bash -r
[root@localhost root]# exit
exit
[root@localhost root]# ant -version
Apache Ant version 1.7.0 compiled on December 13 2006
Now, That is ok!
Install Junit
1 unzip the download zip file
2 set path (/etc/profile and /etc/profile.d/java.sh)
Install PostgreSQL
1 gunzip postgresql-8.2.3.tar.gz
tar xf postgresql-8.2.3.tar
This will create a directory postgresql-8.2.3 under the current directory with the PostgreSQL sources. Change into that directory for the rest of the installation procedure.
2 ./configure
3 gmake
4 gmake check
5 gmake install
6 To undo the installation use the command gmake uninstall. However, this will not remove any created directories.
LD_LIBRARY_PATH=/usr/local/pgsql/lib
export LD_LIBRARY_PATH
8 set enviroment path
PATH=/usr/local/pgsql/bin:$PATH
export PATH
JUnit Test:
*** You must first compile the java classes,afer that, then you can test it!
run the test:
*
To run the test from the console, type:
java org.junit.runner.JUnitCore junitfaq.SimpleTest
*
To run the test with the test runner used in main(), type:
java junitfaq.SimpleTest
The passing test results in the following textual output:
.
Time: 0
OK (1 tests)
(3): intro_postgreSQL_startup
Before you can do anything, you must initialize a database storage area on disk. We call this a database cluster.
The desired file system location of your database cluster is indicated by the -D option, for example:
$ initdb -D /usr/local/pgsql/data
anyone can access the database, you must start the database server. The
database server program is called postgres. The postgres program
must know where to find the data it is supposed to use. This is done
with the -D option. Thus, the simplest way to start the server is:
$ postgres -D /usr/local/pgsql/data
which will leave the server running in the foreground. This must be
done while logged into the PostgreSQL user account. Without -D, the
server will try to use the data directory named by the environment
variable PGDATA. If that variable is not provided either, it will fail.
Normally it is better to start postgres in the background. For this, use the usual shell syntax:
$ postgres -D /usr/local/pgsql/data >logfile 2>&1 &
ping -n 5 -l 1472 -f www.sina.com.cn
LDAP:
如果你把 ldap 安裝為 windows 服務,你可以像我一樣啟動:
net start OpenLDAP-slapd
net stop OpenLDAP-slapd
ldapadd -x -D "cn=Manager,dc=grid,dc=nwu" -W -f example.ldif
** 注意: 在第一次添加的時候要講默認的庫 添加進去才可以,否則會報錯誤 Object 找不到!!!
dc=example,dc=com is not there by default, you have to add it as the first entry.
Your when you are trying to add you are assuming dc=example,dc=com is there,
just remove the fred walter and try again
將每一個目錄ENTRY看作是一個統一表的一行,然后把屬性看作列
FAQ:
1:
windows下的openldap,filter 不能家單引號 如下(去掉單引號)
ldapsearch -x -b dc=grid,dc=nwu (objectclass=*)
2:
用到的objectclass (處在schema中定義) 必須在 slapd.conf 中include進來,否則報錯誤
3:
dn: cn=JSmith,ou=Managerment,dc=grid,dc=nwu
cn: James Smith,Jim Smith,Jimmy Smith
這里的dn中的cn 和屬性中的cn 必須是一樣的,否則報錯誤:alue of naming attribute 'cn' is not present in entry
4:擴展schema
http://blog.csdn.net/wcy1323/archive/2006/04/26/678459.aspx
synchronized是利用鎖的機制,使變量或代碼塊在某一時該只能被一個線程訪問。
而ThreadLocal為每一個線程都提供了變量的副本,使得每個線程在某一時間訪問到的并不是同一個對象,
這樣就隔離了多個線程對數據的數據共享。
而Synchronized卻正好相反,它用于在多個線程間通信時能夠獲得數據共享。
Synchronized用于線程間的數據共享,而ThreadLocal則用于線程間的數據隔離。
當然ThreadLocal并不能替代synchronized,它們處理不同的問題域。
Synchronized用于實現同步機制,比ThreadLocal更加復雜。
It turns out that every JavaScript object includes an internal reference to another object, known as its prototype object. Any properties of the prototype appear to be properties of an object for which it is the prototype. Another way of saying this is that a JavaScript object inherits properties from its prototype.
In the previous section, I showed that the new operator creates a new, empty object and then invokes a constructor function as a method of that object. This is not the complete story, however. After creating the empty object, new sets the prototype of that object. The prototype of an object is the value of the prototype property of its constructor function. All functions have a prototype property that is automatically created and initialized when the function is defined. The initial value of the prototype property is an object with a single property. This property is named constructor and refers back to the constructor function with which the prototype is associated. (You may recall the constructor property from Chapter 7 ; this is why every object has a constructor property.) Any properties you add to this prototype object will appear to be properties of objects initialized by the constructor.
對要訪問的同步資源進行 同步計數控制,來達到同步訪問資源的目的。
1:互斥條件 ,即資源是不能夠被共享的。
2:至少有一個進程在使用一個資源卻在等待另外一個線程所持有的一個資源
3:資源部能夠被進程搶占。
4 :必須有循環的等待
那么要解除死鎖,只要讓這幾個條件中的一個不成立就可以了。例如:
哲學家問題,如果每個哲學家都是先 取左邊的筷子,在取右邊的筷子,那么很有可能就會出現死鎖了,那么我們可以讓最后的一個哲學家是先取右邊的筷子,然后再取左邊的筷子,那么就破壞了循環等待的原則,那么死鎖自然也就不會成立了 。當然線程也可以一次獲得所有的所需資源來實現了。
volatile
do?
This is probably best explained by comparing the effects that volatile
and synchronized
have on a method. volatile
is a field modifier, while synchronized
modifies code blocks and methods. So we can specify three variations of a simple accessor using those two keywords:
int i1; int geti1() {return i1;} volatile int i2; int geti2() {return i2;} int i3; synchronized int geti3() {return i3;}
geti1()
accesses the value currently stored in i1
in the current thread. Threads can have local copies of variables, and the data does not have to be the same as the data held in other threads. In particular, another thread may have updated i1
in it's thread, but the value in the current thread could be different from that updated value. In fact Java has the idea of a "main" memory, and this is the memory that holds the current "correct" value for variables. Threads can have their own copy of data for variables, and the thread copy can be different from the "main" memory. So in fact, it is possible for the "main" memory to have a value of 1 for i1
, for thread1 to have a value of 2 for i1
and for thread2 to have a value of 3 for i1
if thread1 and thread2 have both updated i1
but those updated value has not yet been propagated to "main" memory or other threads.
On the other hand, geti2()
effectively accesses the value of i2
from "main" memory. A volatile
variable is not allowed to have a local copy of a variable that is different from the value currently held in "main" memory. Effectively, a variable declared volatile
must have it's data synchronized across all threads, so that whenever you access or update the variable in any thread, all other threads immediately see the same value. Of course, it is likely that volatile
variables have a higher access and update overhead than "plain" variables, since the reason threads can have their own copy of data is for better efficiency.
Well if volatile
already synchronizes data across threads, what is synchronized
for? Well there are two differences. Firstly synchronized
obtains and releases locks on monitors which can force only one thread at a time to execute a code block, if both threads use the same monitor (effectively the same object lock). That's the fairly well known aspect to synchronized
. But synchronized
also synchronizes memory. In fact synchronized
synchronizes the whole of thread memory with "main" memory. So executing geti3()
does the following:
this
(assuming the monitor is unlocked, otherwise the thread waits until the monitor is unlocked).
i3
, which may have just been reset from "main" memory).
geti3()
we have no changes.)
this
. So where volatile
only synchronizes the value of one variable between thread memory and "main" memory, synchronized
synchronizes the value of all variables between thread memory and "main" memory, and locks and releases a monitor to boot. Clearly synchronized
is likely to have more overhead than volatile
.
Visual C++線程同步技術剖析:臨界區,時間,信號量,互斥量 | |
|
摘要: 多線程同步技術是計算機軟件開發的重要技術,本文對多線程的各種同步技術的原理和實現進行了初步探討。
關鍵詞: VC++6.0; 線程同步;臨界區;事件;互斥;信號量;
正文
使線程同步
在程序中使用多線程時,一般很少有多個線程能在其生命期內進行完全獨立的操作。更多的情況是一些線程進行某些處理操作,而其他的線程必須對其處理結果進行了解。正常情況下對這種處理結果的了解應當在其處理任務完成后進行。
如果不采取適當的措施,其他線程往往會在線程處理任務結束前就去訪問處理結果,這就很有可能得到有關處理結果的錯誤了解。例如,多個線程同時訪問同一個全局變量,如果都是讀取操作,則不會出現問題。如果一個線程負責改變此變量的值,而其他線程負責同時讀取變量內容,則不能保證讀取到的數據是經過寫線程修改后的。
為了確保讀線程讀取到的是經過修改的變量,就必須在向變量寫入數據時禁止其他線程對其的任何訪問,直至賦值過程結束后再解除對其他線程的訪問限制。象這種保證線程能了解其他線程任務處理結束后的處理結果而采取的保護措施即為線程同步。
線程同步是一個非常大的話題,包括方方面面的內容。從大的方面講,線程的同步可分用戶模式的線程同步和內核對象的線程同步兩大類。用戶模式中線程的同步方法主要有原子訪問和臨界區等方法。其特點是同步速度特別快,適合于對線程運行速度有嚴格要求的場合。
內核對象的線程同步則主要由事件、等待定時器、信號量以及信號燈等內核對象構成。由于這種同步機制使用了內核對象,使用時必須將線程從用戶模式切換到內核模式,而這種轉換一般要耗費近千個CPU周期,因此同步速度較慢,但在適用性上卻要遠優于用戶模式的線程同步方式。
臨界區
臨界區(Critical Section)是一段獨占對某些共享資源訪問的代碼,在任意時刻只允許一個線程對共享資源進行訪問。如果有多個線程試圖同時訪問臨界區,那么在有一個線程進入后其他所有試圖訪問此臨界區的線程將被掛起,并一直持續到進入臨界區的線程離開。臨界區在被釋放后,其他線程可以繼續搶占,并以此達到用原子方式操作共享資源的目的。
臨界區在使用時以CRITICAL_SECTION結構對象保護共享資源,并分別用EnterCriticalSection()和LeaveCriticalSection()函數去標識和釋放一個臨界區。所用到的CRITICAL_SECTION結構對象必須經過InitializeCriticalSection()的初始化后才能使用,而且必須確保所有線程中的任何試圖訪問此共享資源的代碼都處在此臨界區的保護之下。否則臨界區將不會起到應有的作用,共享資源依然有被破壞的可能。
圖1 使用臨界區保持線程同步
下面通過一段代碼展示了臨界區在保護多線程訪問的共享資源中的作用。通過兩個線程來分別對全局變量g_cArray[10]進行寫入操作,用臨界區結構對象g_cs來保持線程的同步,并在開啟線程前對其進行初始化。為了使實驗效果更加明顯,體現出臨界區的作用,在線程函數對共享資源g_cArray[10]的寫入時,以Sleep()函數延遲1毫秒,使其他線程同其搶占CPU的可能性增大。如果不使用臨界區對其進行保護,則共享資源數據將被破壞(參見圖1(a)所示計算結果),而使用臨界區對線程保持同步后則可以得到正確的結果(參見圖1(b)所示計算結果)。代碼實現清單附下:
// 臨界區結構對象 CRITICAL_SECTION g_cs; // 共享資源 char g_cArray[10]; UINT ThreadProc10(LPVOID pParam) { // 進入臨界區 EnterCriticalSection(&g_cs); // 對共享資源進行寫入操作 for (int i = 0; i < 10; i++) { g_cArray[i] = 'a'; Sleep(1); } // 離開臨界區 LeaveCriticalSection(&g_cs); return 0; } UINT ThreadProc11(LPVOID pParam) { // 進入臨界區 EnterCriticalSection(&g_cs); // 對共享資源進行寫入操作 for (int i = 0; i < 10; i++) { g_cArray[10 - i - 1] = 'b'; Sleep(1); } // 離開臨界區 LeaveCriticalSection(&g_cs); return 0; } …… void CSample08View::OnCriticalSection() { // 初始化臨界區 InitializeCriticalSection(&g_cs); // 啟動線程 AfxBeginThread(ThreadProc10, NULL); AfxBeginThread(ThreadProc11, NULL); // 等待計算完畢 Sleep(300); // 報告計算結果 CString sResult = CString(g_cArray); AfxMessageBox(sResult); } |
在使用臨界區時,一般不允許其運行時間過長,只要進入臨界區的線程還沒有離開,其他所有試圖進入此臨界區的線程都會被掛起而進入到等待狀態,并會在一定程度上影響。程序的運行性能。尤其需要注意的是不要將等待用戶輸入或是其他一些外界干預的操作包含到臨界區。如果進入了臨界區卻一直沒有釋放,同樣也會引起其他線程的長時間等待。換句話說,在執行了EnterCriticalSection()語句進入臨界區后無論發生什么,必須確保與之匹配的LeaveCriticalSection()都能夠被執行到。可以通過添加結構化異常處理代碼來確保LeaveCriticalSection()語句的執行。雖然臨界區同步速度很快,但卻只能用來同步本進程內的線程,而不可用來同步多個進程中的線程。
MFC為臨界區提供有一個CCriticalSection類,使用該類進行線程同步處理是非常簡單的,只需在線程函數中用CCriticalSection類成員函數Lock()和UnLock()標定出被保護代碼片段即可。對于上述代碼,可通過CCriticalSection類將其改寫如下:
// MFC臨界區類對象 CCriticalSection g_clsCriticalSection; // 共享資源 char g_cArray[10]; UINT ThreadProc20(LPVOID pParam) { // 進入臨界區 g_clsCriticalSection.Lock(); // 對共享資源進行寫入操作 for (int i = 0; i < 10; i++) { g_cArray[i] = 'a'; Sleep(1); } // 離開臨界區 g_clsCriticalSection.Unlock(); return 0; } UINT ThreadProc21(LPVOID pParam) { // 進入臨界區 g_clsCriticalSection.Lock(); // 對共享資源進行寫入操作 for (int i = 0; i < 10; i++) { g_cArray[10 - i - 1] = 'b'; Sleep(1); } // 離開臨界區 g_clsCriticalSection.Unlock(); return 0; } …… void CSample08View::OnCriticalSectionMfc() { // 啟動線程 AfxBeginThread(ThreadProc20, NULL); AfxBeginThread(ThreadProc21, NULL); // 等待計算完畢 Sleep(300); // 報告計算結果 CString sResult = CString(g_cArray); AfxMessageBox(sResult); } |
管理事件內核對象
在前面講述線程通信時曾使用過事件內核對象來進行線程間的通信,除此之外,事件內核對象也可以通過通知操作的方式來保持線程的同步。對于前面那段使用臨界區保持線程同步的代碼可用事件對象的線程同步方法改寫如下:
// 事件句柄 HANDLE hEvent = NULL; // 共享資源 char g_cArray[10]; …… UINT ThreadProc12(LPVOID pParam) { // 等待事件置位 WaitForSingleObject(hEvent, INFINITE); // 對共享資源進行寫入操作 for (int i = 0; i < 10; i++) { g_cArray[i] = 'a'; Sleep(1); } // 處理完成后即將事件對象置位 SetEvent(hEvent); return 0; } UINT ThreadProc13(LPVOID pParam) { // 等待事件置位 WaitForSingleObject(hEvent, INFINITE); // 對共享資源進行寫入操作 for (int i = 0; i < 10; i++) { g_cArray[10 - i - 1] = 'b'; Sleep(1); } // 處理完成后即將事件對象置位 SetEvent(hEvent); return 0; } …… void CSample08View::OnEvent() { // 創建事件 hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); // 事件置位 SetEvent(hEvent); // 啟動線程 AfxBeginThread(ThreadProc12, NULL); AfxBeginThread(ThreadProc13, NULL); // 等待計算完畢 Sleep(300); // 報告計算結果 CString sResult = CString(g_cArray); AfxMessageBox(sResult); } |
在創建線程前,首先創建一個可以自動復位的事件內核對象hEvent,而線程函數則通過WaitForSingleObject()等待函數無限等待hEvent的置位,只有在事件置位時WaitForSingleObject()才會返回,被保護的代碼將得以執行。對于以自動復位方式創建的事件對象,在其置位后一被WaitForSingleObject()等待到就會立即復位,也就是說在執行ThreadProc12()中的受保護代碼時,事件對象已經是復位狀態的,這時即使有ThreadProc13()對CPU的搶占,也會由于WaitForSingleObject()沒有hEvent的置位而不能繼續執行,也就沒有可能破壞受保護的共享資源。在ThreadProc12()中的處理完成后可以通過SetEvent()對hEvent的置位而允許ThreadProc13()對共享資源g_cArray的處理。這里SetEvent()所起的作用可以看作是對某項特定任務完成的通知。
使用臨界區只能同步同一進程中的線程,而使用事件內核對象則可以對進程外的線程進行同步,其前提是得到對此事件對象的訪問權。可以通過OpenEvent()函數獲取得到,其函數原型為:
HANDLE OpenEvent( DWORD dwDesiredAccess, // 訪問標志 BOOL bInheritHandle, // 繼承標志 LPCTSTR lpName // 指向事件對象名的指針 ); |
如果事件對象已創建(在創建事件時需要指定事件名),函數將返回指定事件的句柄。對于那些在創建事件時沒有指定事件名的事件內核對象,可以通過使用內核對象的繼承性或是調用DuplicateHandle()函數來調用CreateEvent()以獲得對指定事件對象的訪問權。在獲取到訪問權后所進行的同步操作與在同一個進程中所進行的線程同步操作是一樣的。
如果需要在一個線程中等待多個事件,則用WaitForMultipleObjects()來等待。WaitForMultipleObjects()與WaitForSingleObject()類似,同時監視位于句柄數組中的所有句柄。這些被監視對象的句柄享有平等的優先權,任何一個句柄都不可能比其他句柄具有更高的優先權。WaitForMultipleObjects()的函數原型為:
DWORD WaitForMultipleObjects( DWORD nCount, // 等待句柄數 CONST HANDLE *lpHandles, // 句柄數組首地址 BOOL fWaitAll, // 等待標志 DWORD dwMilliseconds // 等待時間間隔 ); |
參數nCount指定了要等待的內核對象的數目,存放這些內核對象的數組由lpHandles來指向。fWaitAll對指定的這nCount個內核對象的兩種等待方式進行了指定,為TRUE時當所有對象都被通知時函數才會返回,為FALSE則只要其中任何一個得到通知就可以返回。dwMilliseconds在飫锏淖饔糜朐赪aitForSingleObject()中的作用是完全一致的。如果等待超時,函數將返回WAIT_TIMEOUT。如果返回WAIT_OBJECT_0到WAIT_OBJECT_0+nCount-1中的某個值,則說明所有指定對象的狀態均為已通知狀態(當fWaitAll為TRUE時)或是用以減去WAIT_OBJECT_0而得到發生通知的對象的索引(當fWaitAll為FALSE時)。如果返回值在WAIT_ABANDONED_0與WAIT_ABANDONED_0+nCount-1之間,則表示所有指定對象的狀態均為已通知,且其中至少有一個對象是被丟棄的互斥對象(當fWaitAll為TRUE時),或是用以減去WAIT_OBJECT_0表示一個等待正常結束的互斥對象的索引(當fWaitAll為FALSE時)。 下面給出的代碼主要展示了對WaitForMultipleObjects()函數的使用。通過對兩個事件內核對象的等待來控制線程任務的執行與中途退出:
// 存放事件句柄的數組 HANDLE hEvents[2]; UINT ThreadProc14(LPVOID pParam) { // 等待開啟事件 DWORD dwRet1 = WaitForMultipleObjects(2, hEvents, FALSE, INFINITE); // 如果開啟事件到達則線程開始執行任務 if (dwRet1 == WAIT_OBJECT_0) { AfxMessageBox("線程開始工作!"); while (true) { for (int i = 0; i < 10000; i++); // 在任務處理過程中等待結束事件 DWORD dwRet2 = WaitForMultipleObjects(2, hEvents, FALSE, 0); // 如果結束事件置位則立即終止任務的執行 if (dwRet2 == WAIT_OBJECT_0 + 1) break; } } AfxMessageBox("線程退出!"); return 0; } …… void CSample08View::OnStartEvent() { // 創建線程 for (int i = 0; i < 2; i++) hEvents[i] = CreateEvent(NULL, FALSE, FALSE, NULL); // 開啟線程 AfxBeginThread(ThreadProc14, NULL); // 設置事件0(開啟事件) SetEvent(hEvents[0]); } void CSample08View::OnEndevent() { // 設置事件1(結束事件) SetEvent(hEvents[1]); } |
MFC為事件相關處理也提供了一個CEvent類,共包含有除構造函數外的4個成員函數PulseEvent()、ResetEvent()、SetEvent()和UnLock()。在功能上分別相當與Win32 API的PulseEvent()、ResetEvent()、SetEvent()和CloseHandle()等函數。而構造函數則履行了原CreateEvent()函數創建事件對象的職責,其函數原型為:
CEvent(BOOL bInitiallyOwn = FALSE, BOOL bManualReset = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL ); |
按照此缺省設置將創建一個自動復位、初始狀態為復位狀態的沒有名字的事件對象。封裝后的CEvent類使用起來更加方便,圖2即展示了CEvent類對A、B兩線程的同步過程:
圖2 CEvent類對線程的同步過程示意
B線程在執行到CEvent類成員函數Lock()時將會發生阻塞,而A線程此時則可以在沒有B線程干擾的情況下對共享資源進行處理,并在處理完成后通過成員函數SetEvent()向B發出事件,使其被釋放,得以對A先前已處理完畢的共享資源進行操作。可見,使用CEvent類對線程的同步方法與通過API函數進行線程同步的處理方法是基本一致的。前面的API處理代碼可用CEvent類將其改寫為:
// MFC事件類對象
|
信號量內核對象
信號量(Semaphore)內核對象對線程的同步方式與前面幾種方法不同,它允許多個線程在同一時刻訪問同一資源,但是需要限制在同一時刻訪問此資源的最大線程數目。在用CreateSemaphore()創建信號量時即要同時指出允許的最大資源計數和當前可用資源計數。一般是將當前可用資源計數設置為最大資源計數,每增加一個線程對共享資源的訪問,當前可用資源計數就會減1,只要當前可用資源計數是大于0的,就可以發出信號量信號。但是當前可用計數減小到0時則說明當前占用資源的線程數已經達到了所允許的最大數目,不能在允許其他線程的進入,此時的信號量信號將無法發出。線程在處理完共享資源后,應在離開的同時通過ReleaseSemaphore()函數將當前可用資源計數加1。在任何時候當前可用資源計數決不可能大于最大資源計數。
圖3 使用信號量對象控制資源
下面結合圖例3來演示信號量對象對資源的控制。在圖3中,以箭頭和白色箭頭表示共享資源所允許的最大資源計數和當前可用資源計數。初始如圖(a)所示,最大資源計數和當前可用資源計數均為4,此后每增加一個對資源進行訪問的線程(用黑色箭頭表示)當前資源計數就會相應減1,圖(b)即表示的在3個線程對共享資源進行訪問時的狀態。當進入線程數達到4個時,將如圖(c)所示,此時已達到最大資源計數,而當前可用資源計數也已減到0,其他線程無法對共享資源進行訪問。在當前占有資源的線程處理完畢而退出后,將會釋放出空間,圖(d)已有兩個線程退出對資源的占有,當前可用計數為2,可以再允許2個線程進入到對資源的處理。可以看出,信號量是通過計數來對線程訪問資源進行控制的,而實際上信號量確實也被稱作Dijkstra計數器。
使用信號量內核對象進行線程同步主要會用到CreateSemaphore()、OpenSemaphore()、ReleaseSemaphore()、WaitForSingleObject()和WaitForMultipleObjects()等函數。其中,CreateSemaphore()用來創建一個信號量內核對象,其函數原型為:
HANDLE CreateSemaphore( LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // 安全屬性指針 LONG lInitialCount, // 初始計數 LONG lMaximumCount, // 最大計數 LPCTSTR lpName // 對象名指針 ); |
參數lMaximumCount是一個有符號32位值,定義了允許的最大資源計數,最大取值不能超過4294967295。lpName參數可以為創建的信號量定義一個名字,由于其創建的是一個內核對象,因此在其他進程中可以通過該名字而得到此信號量。OpenSemaphore()函數即可用來根據信號量名打開在其他進程中創建的信號量,函數原型如下:
HANDLE OpenSemaphore( DWORD dwDesiredAccess, // 訪問標志 BOOL bInheritHandle, // 繼承標志 LPCTSTR lpName // 信號量名 ); |
在線程離開對共享資源的處理時,必須通過ReleaseSemaphore()來增加當前可用資源計數。否則將會出現當前正在處理共享資源的實際線程數并沒有達到要限制的數值,而其他線程卻因為當前可用資源計數為0而仍無法進入的情況。ReleaseSemaphore()的函數原型為:
BOOL ReleaseSemaphore( HANDLE hSemaphore, // 信號量句柄 LONG lReleaseCount, // 計數遞增數量 LPLONG lpPreviousCount // 先前計數 ); |
該函數將lReleaseCount中的值添加給信號量的當前資源計數,一般將lReleaseCount設置為1,如果需要也可以設置其他的值。WaitForSingleObject()和WaitForMultipleObjects()主要用在試圖進入共享資源的線程函數入口處,主要用來判斷信號量的當前可用資源計數是否允許本線程的進入。只有在當前可用資源計數值大于0時,被監視的信號量內核對象才會得到通知。
信號量的使用特點使其更適用于對Socket(套接字)程序中線程的同步。例如,網絡上的HTTP服務器要對同一時間內訪問同一頁面的用戶數加以限制,這時可以為沒一個用戶對服務器的頁面請求設置一個線程,而頁面則是待保護的共享資源,通過使用信號量對線程的同步作用可以確保在任一時刻無論有多少用戶對某一頁面進行訪問,只有不大于設定的最大用戶數目的線程能夠進行訪問,而其他的訪問企圖則被掛起,只有在有用戶退出對此頁面的訪問后才有可能進入。下面給出的示例代碼即展示了類似的處理過程:
// 信號量對象句柄
HANDLE hSemaphore;
UINT ThreadProc15(LPVOID pParam)
{
// 試圖進入信號量關口
WaitForSingleObject(hSemaphore, INFINITE);
// 線程任務處理
AfxMessageBox("線程一正在執行!");
// 釋放信號量計數
ReleaseSemaphore(hSemaphore, 1, NULL);
return 0;
}
UINT ThreadProc16(LPVOID pParam)
{
// 試圖進入信號量關口
WaitForSingleObject(hSemaphore, INFINITE);
// 線程任務處理
AfxMessageBox("線程二正在執行!");
// 釋放信號量計數
ReleaseSemaphore(hSemaphore, 1, NULL);
return 0;
}
UINT ThreadProc17(LPVOID pParam)
{
// 試圖進入信號量關口
WaitForSingleObject(hSemaphore, INFINITE);
// 線程任務處理
AfxMessageBox("線程三正在執行!");
// 釋放信號量計數
ReleaseSemaphore(hSemaphore, 1, NULL);
return 0;
}
……
void CSample08View::OnSemaphore()
{
// 創建信號量對象
hSemaphore = CreateSemaphore(NULL, 2, 2, NULL);
// 開啟線程
AfxBeginThread(ThreadProc15, NULL);
AfxBeginThread(ThreadProc16, NULL);
AfxBeginThread(ThreadProc17, NULL);
}
圖4 開始進入的兩個線程
圖5 線程二退出后線程三才得以進入
上述代碼在開啟線程前首先創建了一個初始計數和最大資源計數均為2的信號量對象hSemaphore。即在同一時刻只允許2個線程進入由hSemaphore保護的共享資源。隨后開啟的三個線程均試圖訪問此共享資源,在前兩個線程試圖訪問共享資源時,由于hSemaphore的當前可用資源計數分別為2和1,此時的hSemaphore是可以得到通知的,也就是說位于線程入口處的WaitForSingleObject()將立即返回,而在前兩個線程進入到保護區域后,hSemaphore的當前資源計數減少到0,hSemaphore將不再得到通知,WaitForSingleObject()將線程掛起。直到此前進入到保護區的線程退出后才能得以進入。圖4和圖5為上述代脈的運行結果。從實驗結果可以看出,信號量始終保持了同一時刻不超過2個線程的進入。
在MFC中,通過CSemaphore類對信號量作了表述。該類只具有一個構造函數,可以構造一個信號量對象,并對初始資源計數、最大資源計數、對象名和安全屬性等進行初始化,其原型如下:
CSemaphore( LONG lInitialCount = 1, LONG lMaxCount = 1, LPCTSTR pstrName = NULL, LPSECURITY_ATTRIBUTES lpsaAttributes = NULL ); |
在構造了CSemaphore類對象后,任何一個訪問受保護共享資源的線程都必須通過CSemaphore從父類CSyncObject類繼承得到的Lock()和UnLock()成員函數來訪問或釋放CSemaphore對象。與前面介紹的幾種通過MFC類保持線程同步的方法類似,通過CSemaphore類也可以將前面的線程同步代碼進行改寫,這兩種使用信號量的線程同步方法無論是在實現原理上還是從實現結果上都是完全一致的。下面給出經MFC改寫后的信號量線程同步代碼:
// MFC信號量類對象 CSemaphore g_clsSemaphore(2, 2); UINT ThreadProc24(LPVOID pParam) { // 試圖進入信號量關口 g_clsSemaphore.Lock(); // 線程任務處理 AfxMessageBox("線程一正在執行!"); // 釋放信號量計數 g_clsSemaphore.Unlock(); return 0; } UINT ThreadProc25(LPVOID pParam) { // 試圖進入信號量關口 g_clsSemaphore.Lock(); // 線程任務處理 AfxMessageBox("線程二正在執行!"); // 釋放信號量計數 g_clsSemaphore.Unlock(); return 0; } UINT ThreadProc26(LPVOID pParam) { // 試圖進入信號量關口 g_clsSemaphore.Lock(); // 線程任務處理 AfxMessageBox("線程三正在執行!"); // 釋放信號量計數 g_clsSemaphore.Unlock(); return 0; } …… void CSample08View::OnSemaphoreMfc() { // 開啟線程 AfxBeginThread(ThreadProc24, NULL); AfxBeginThread(ThreadProc25, NULL); AfxBeginThread(ThreadProc26, NULL); } |
互斥內核對象
互斥(Mutex)是一種用途非常廣泛的內核對象。能夠保證多個線程對同一共享資源的互斥訪問。同臨界區有些類似,只有擁有互斥對象的線程才具有訪問資源的權限,由于互斥對象只有一個,因此就決定了任何情況下此共享資源都不會同時被多個線程所訪問。當前占據資源的線程在任務處理完后應將擁有的互斥對象交出,以便其他線程在獲得后得以訪問資源。與其他幾種內核對象不同,互斥對象在操作系統中擁有特殊代碼,并由操作系統來管理,操作系統甚至還允許其進行一些其他內核對象所不能進行的非常規操作。為便于理解,可參照圖6給出的互斥內核對象的工作模型:
圖6 使用互斥內核對象對共享資源的保護
圖(a)中的箭頭為要訪問資源(矩形框)的線程,但只有第二個線程擁有互斥對象(黑點)并得以進入到共享資源,而其他線程則會被排斥在外(如圖(b)所示)。當此線程處理完共享資源并準備離開此區域時將把其所擁有的互斥對象交出(如圖(c)所示),其他任何一個試圖訪問此資源的線程都有機會得到此互斥對象。
以互斥內核對象來保持線程同步可能用到的函數主要有CreateMutex()、OpenMutex()、ReleaseMutex()、WaitForSingleObject()和WaitForMultipleObjects()等。在使用互斥對象前,首先要通過CreateMutex()或OpenMutex()創建或打開一個互斥對象。CreateMutex()函數原型為:
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes, // 安全屬性指針
BOOL bInitialOwner, // 初始擁有者
LPCTSTR lpName // 互斥對象名
);
參數bInitialOwner主要用來控制互斥對象的初始狀態。一般多將其設置為FALSE,以表明互斥對象在創建時并沒有為任何線程所占有。如果在創建互斥對象時指定了對象名,那么可以在本進程其他地方或是在其他進程通過OpenMutex()函數得到此互斥對象的句柄。OpenMutex()函數原型為:
HANDLE OpenMutex(
DWORD dwDesiredAccess, // 訪問標志
BOOL bInheritHandle, // 繼承標志
LPCTSTR lpName // 互斥對象名
);
當目前對資源具有訪問權的線程不再需要訪問此資源而要離開時,必須通過ReleaseMutex()函數來釋放其擁有的互斥對象,其函數原型為:
BOOL ReleaseMutex(HANDLE hMutex);
其唯一的參數hMutex為待釋放的互斥對象句柄。至于WaitForSingleObject()和WaitForMultipleObjects()等待函數在互斥對象保持線程同步中所起的作用與在其他內核對象中的作用是基本一致的,也是等待互斥內核對象的通知。但是這里需要特別指出的是:在互斥對象通知引起調用等待函數返回時,等待函數的返回值不再是通常的WAIT_OBJECT_0(對于WaitForSingleObject()函數)或是在WAIT_OBJECT_0到WAIT_OBJECT_0+nCount-1之間的一個值(對于WaitForMultipleObjects()函數),而是將返回一個WAIT_ABANDONED_0(對于WaitForSingleObject()函數)或是在WAIT_ABANDONED_0到WAIT_ABANDONED_0+nCount-1之間的一個值(對于WaitForMultipleObjects()函數)。以此來表明線程正在等待的互斥對象由另外一個線程所擁有,而此線程卻在使用完共享資源前就已經終止。除此之外,使用互斥對象的方法在等待線程的可調度性上同使用其他幾種內核對象的方法也有所不同,其他內核對象在沒有得到通知時,受調用等待函數的作用,線程將會掛起,同時失去可調度性,而使用互斥的方法卻可以在等待的同時仍具有可調度性,這也正是互斥對象所能完成的非常規操作之一。
在編寫程序時,互斥對象多用在對那些為多個線程所訪問的內存塊的保護上,可以確保任何線程在處理此內存塊時都對其擁有可靠的獨占訪問權。下面給出的示例代碼即通過互斥內核對象hMutex對共享內存快g_cArray[]進行線程的獨占訪問保護。下面給出實現代碼清單:
// 互斥對象
HANDLE hMutex = NULL;
char g_cArray[10];
UINT ThreadProc18(LPVOID pParam)
{
// 等待互斥對象通知
WaitForSingleObject(hMutex, INFINITE);
// 對共享資源進行寫入操作
for (int i = 0; i < 10; i++)
{
g_cArray[i] = 'a';
Sleep(1);
}
// 釋放互斥對象
ReleaseMutex(hMutex);
return 0;
}
UINT ThreadProc19(LPVOID pParam)
{
// 等待互斥對象通知
WaitForSingleObject(hMutex, INFINITE);
// 對共享資源進行寫入操作
for (int i = 0; i < 10; i++)
{
g_cArray[10 - i - 1] = 'b';
Sleep(1);
}
// 釋放互斥對象
ReleaseMutex(hMutex);
return 0;
}
……
void CSample08View::OnMutex()
{
// 創建互斥對象
hMutex = CreateMutex(NULL, FALSE, NULL);
// 啟動線程
AfxBeginThread(ThreadProc18, NULL);
AfxBeginThread(ThreadProc19, NULL);
// 等待計算完畢
Sleep(300);
// 報告計算結果
CString sResult = CString(g_cArray);
AfxMessageBox(sResult);
}
互斥對象在MFC中通過CMutex類進行表述。使用CMutex類的方法非常簡單,在構造CMutex類對象的同時可以指明待查詢的互斥對象的名字,在構造函數返回后即可訪問此互斥變量。CMutex類也是只含有構造函數這唯一的成員函數,當完成對互斥對象保護資源的訪問后,可通過調用從父類CSyncObject繼承的UnLock()函數完成對互斥對象的釋放。CMutex類構造函數原型為:
CMutex( BOOL bInitiallyOwn = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL );
該類的適用范圍和實現原理與API方式創建的互斥內核對象是完全類似的,但要簡潔的多,下面給出就是對前面的示例代碼經CMutex類改寫后的程序實現清單:
// MFC互斥類對象
CMutex g_clsMutex(FALSE, NULL);
UINT ThreadProc27(LPVOID pParam)
{
// 等待互斥對象通知
g_clsMutex.Lock();
// 對共享資源進行寫入操作
for (int i = 0; i < 10; i++)
{
g_cArray[i] = 'a';
Sleep(1);
}
// 釋放互斥對象
g_clsMutex.Unlock();
return 0;
}
UINT ThreadProc28(LPVOID pParam)
{
// 等待互斥對象通知
g_clsMutex.Lock();
// 對共享資源進行寫入操作
for (int i = 0; i < 10; i++)
{
g_cArray[10 - i - 1] = 'b';
Sleep(1);
}
// 釋放互斥對象
g_clsMutex.Unlock();
return 0;
}
……
void CSample08View::OnMutexMfc()
{
// 啟動線程
AfxBeginThread(ThreadProc27, NULL);
AfxBeginThread(ThreadProc28, NULL);
// 等待計算完畢
Sleep(300);
// 報告計算結果
CString sResult = CString(g_cArray);
AfxMessageBox(sResult);
}
小結
線程的使用使程序處理更夠更加靈活,而這種靈活同樣也會帶來各種不確定性的可能。尤其是在多個線程對同一公共變量進行訪問時。雖然未使用線程同步的程序代碼在邏輯上或許沒有什么問題,但為了確保程序的正確、可靠運行,必須在適當的場合采取線程同步措施。
public class EagerSingleton {
String name;
private static final EagerSingleton m_instance = new EagerSingleton();
private EagerSingleton()
{
name ="wqh";
}
public static EagerSingleton getInstance()
{
return m_instance;
}
public static void main(String[] args) {
EagerSingleton es ;
es = EagerSingleton.getInstance();
System.out.println(es.name);
}
}
在有一種呢就是在該類型里面先聲明一個對象,如何通過一個同步的static方法每次返回同一個 對象,這也就是 所謂的懶漢模式,如下:
public class LazySingleton {
private static LazySingleton m_instance = null;
private LazySingleton()
{
}
synchronized public static LazySingleton getInstance()
{
if(m_instance == null)
{
m_instance = new LazySingleton();
}
return m_instance;
}
public static void main(String[] args) {
LazySingleton ls;
ls = LazySingleton.getInstance();
System.out.println(ls.getClass());
}
}
ArrayList 是由數組實現的 list , 他容許對元素進行快速的隨機訪問,但是插入與移除 的速度很慢。ListIterator 只應該用來從后往前 遍歷 ArrayList ,而不能夠做移除。
LinkedList 對順序訪問進行了優化,向List 插入與移除的開銷并不大,隨機訪問則相對較慢。
Set (interface)存入Set 的每個元素都必須是唯一的,不保存重復元素。加入對象必須定義eqauls()方法保證唯一性。不保證維護元素的次序。
HsahSet 為快速查找而設計的 Set 存入HashSet的對象必須定義 hsahCode()方法
TreeSet 保持次序的Set,底層微樹結構,使用他可以從Set中提取有序的序列
LinkedHashSet 具有hashSet 的查詢速度,且內部使用鏈表維護元素的順序(插入的次序)
Map(interface) 維護鍵值對的關聯性。
HashMap 基于散列表的實現(取代HashTable),插入和查詢鍵值對的開銷固定,可以通過構造器設置容量和負載因子以調整容器性能。
LinkedHashMap 類似HashMap ,但是疊帶遍歷時取得 鍵值對 的順序是插入的次序或者是 最近最少使用的次序。使用鏈表維護內部次序。
TreeMap基于紅黑樹實現,所得到的結果是經過排序的。