#
在使用VC 2005 的開發者會遇到這樣的問題,在使用std命名空間庫函數的時候,往往會出現類似于下面的警告:
warning C4996: strcpy was declared deprecated
出現這樣的警告,是因為VC2005中認為CRT中的一組函數如果使用不當,可能會產生諸如內存泄露、緩沖區溢出、非法訪問等安全問題。這些函數如:strcpy、strcat等。
對于這些問題,VC2005建議使用這些函數的更高級的安全版本,即在這些函數名后面加了一個_s的函數。這些安全版本函數使用起來更有效,也便于識別,如:strcpy_s,calloc_s等。
當然,如果執意使用老版本、非安全版本函數,可以使用_CRT_SECURE_NO_DEPRECATE標記來忽略這些警告問題。辦法是在編譯選項 C/C++ | Preprocessor | Preprocessor Definitions中,增加_CRT_SECURE_NO_DEPRECATE標記即可。
補充:
然而,本以為上面的說法是件漂亮的法子,不想用后不爽。遂用舊法:
#pragma warning(disable:4996) //全部關掉
#pragma warning(once:4996) //僅顯示一個
關于這個話題網上流傳的是一個相同的版本,就是那個第一項是頭文件的區別,但后面列出的頭文件只有#include沒有(估計是原版的在不斷轉載的過程中有人不小心忘了把尖括號轉義,讓瀏覽器當html標記解析沒了)的那個。現在整理了一下,以后也會不斷補充內容。
1)頭文件
windows下winsock.h或winsock2.h
linux下netinet/in.h(大部分都在這兒),unistd.h(close函數在這兒),sys/socket.h(在in.h里已經包含了,可以省了)
2)初始化
windows下需要用WSAStartup啟動Ws2_32.lib,并且要用#pragma comment(lib,"Ws2_32")來告知編譯器鏈接該lib。
linux下不需要
3)關閉socket
windows下closesocket(...)
linux下close(...)
4)類型
windows下SOCKET
linux下int(我喜歡用long,這樣保證是4byte,因為-1我總喜歡寫成0xFFFF)
5)獲取錯誤碼
windows下getlasterror()/WSAGetLastError()
linux下,未能成功執行的socket操作會返回-1;如果包含了errno.h,就會設置errno變量
6)設置非阻塞
windows下ioctlsocket()
linux下fcntl(),需要頭文件fcntl.h
7)send函數最后一個參數
windows下一般設置為0
linux下最好設置為MSG_NOSIGNAL,如果不設置,在發送出錯后有可能會導致程序退出
8)毫秒級時間獲取
windows下GetTickCount()
linux下gettimeofday()
9)多線程
windows下包含process.h,使用_beginthread和_endthread
linux下包含pthread.h,使用pthread_create和pthread_exit
10)用IP定義一個地址(sockaddr_in的結構的區別)
windows下addr_var.sin_addr.S_un.S_addr
linux下addr_var.sin_addr.s_addr
而且Winsock里最后那個32bit的S_addr也有幾個以聯合(Union)的形式與它共享內存空間的成員變量(便于以其他方式賦值),而Linux的Socket沒有這個聯合,就是一個32bit的s_addr。遇到那種得到了是4個char的IP的形式(比如127一個,0一個,0一個和1一個共四個char),WinSock可以直接用4個S_b來賦值到S_addr里,而在Linux下,可以用邊向左移位(一下8bit,共四下)邊相加的方法賦值。
11)異常處理
linux下當連接斷開,還發數據的時候,不僅send()的返回值會有反映,而且還會像系統發送一個異常消息,如果不作處理,系統會出BrokePipe,程序會退出。為此,send()函數的最后一個參數可以設MSG_NOSIGNAL,禁止send()函數向系統發送異常消息。
使用條件變量可以以原子方式阻塞線程,直到某個特定條件為真為止。條件變量始終與互斥鎖一起使用。
使用條件變量,線程可以以原子方式阻塞,直到滿足某個條件為止。對條件的測試是在互斥鎖(互斥)的保護下進行的。
如果條件為假,線程通常會基于條件變量阻塞,并以原子方式釋放等待條件變化的互斥鎖。如果另一個線程更改了條件,該線程可能會向相關的條件變量發出信號,從而使一個或多個等待的線程執行以下操作:
在以下情況下,條件變量可用于在進程之間同步線程:
-
線程是在可以寫入的內存中分配的
-
內存由協作進程共享
調度策略可確定喚醒阻塞線程的方式。對于缺省值 SCHED_OTHER,將按優先級順序喚醒線程。
必須設置和初始化條件變量的屬性,然后才能使用條件變量。表 4–4 列出了用于處理條件變量屬性的函數。
表 4–4 條件變量屬性
表 4–5 中顯示了定義條件變量的范圍時 Solaris 線程和 POSIX 線程之間的差異。
表 4–5 條件變量范圍比較
Solaris
|
POSIX
|
定義
|
USYNC_PROCESS
|
PTHREAD_PROCESS_SHARED
|
用于同步該進程和其他進程中的線程
|
USYNC_THREAD
|
PTHREAD_PROCESS_PRIVATE
|
用于僅同步該進程中的線程
|
初始化條件變量屬性
使用 pthread_condattr_init(3C) 可以將與該對象相關聯的屬性初始化為其缺省值。在執行過程中,線程系統會為每個屬性對象分配存儲空間。
pthread_condattr_init 語法
int pthread_condattr_init(pthread_condattr_t *cattr);
#include <pthread.h>
pthread_condattr_t cattr;
int ret;
/* initialize an attribute to default value */
ret = pthread_condattr_init(&cattr);
調用此函數時,pshared 屬性的缺省值為 PTHREAD_PROCESS_PRIVATE。pshared 的該值表示可以在進程內使用已初始化的條件變量。
cattr 的數據類型為 opaque,其中包含一個由系統分配的屬性對象。cattr 范圍可能的值為 PTHREAD_PROCESS_PRIVATE 和 PTHREAD_PROCESS_SHARED。PTHREAD_PROCESS_PRIVATE 是缺省值。
條件變量屬性必須首先由 pthread_condattr_destroy(3C) 重新初始化后才能重用。pthread_condattr_init() 調用會返回指向類型為 opaque 的對象的指針。如果未銷毀該對象,則會導致內存泄漏。
pthread_condattr_init 返回值
pthread_condattr_init() 在成功完成之后會返回零。其他任何返回值都表示出現了錯誤。如果出現以下任一情況,該函數將失敗并返回對應的值。
ENOMEM
描述:
分配的內存不足,無法初始化線程屬性對象。
EINVAL
描述:
cattr 指定的值無效。
刪除條件變量屬性
使用 pthread_condattr_destroy(3C) 可以刪除存儲并使屬性對象無效。
pthread_condattr_destroy 語法
int pthread_condattr_destroy(pthread_condattr_t *cattr);
#include <pthread.h>
pthread_condattr_t cattr;
int ret;
/* destroy an attribute */
ret
= pthread_condattr_destroy(&cattr);
pthread_condattr_destroy 返回值
pthread_condattr_destroy() 在成功完成之后會返回零。其他任何返回值都表示出現了錯誤。如果出現以下情況,該函數將失敗并返回對應的值。
EINVAL
描述:
cattr 指定的值無效。
設置條件變量的范圍
pthread_condattr_setpshared(3C) 可用來將條件變量的范圍設置為進程專用(進程內)或系統范圍內(進程間)。
pthread_condattr_setpshared 語法
int pthread_condattr_setpshared(pthread_condattr_t *cattr,
int pshared);
#include <pthread.h>
pthread_condattr_t cattr;
int ret;
/* all processes */
ret = pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_SHARED);
/* within a process */
ret = pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_PRIVATE);
如果 pshared 屬性在共享內存中設置為 PTHREAD_PROCESS_SHARED,則其所創建的條件變量可以在多個進程中的線程之間共享。此行為與最初的 Solaris 線程實現中 mutex_init() 中的 USYNC_PROCESS 標志等效。
如果互斥鎖的 pshared 屬性設置為 PTHREAD_PROCESS_PRIVATE,則僅有那些由同一個進程創建的線程才能夠處理該互斥鎖。PTHREAD_PROCESS_PRIVATE 是缺省值。PTHREAD_PROCESS_PRIVATE 所產生的行為與在最初的 Solaris 線程的 cond_init() 調用中使用 USYNC_THREAD 標志相同。PTHREAD_PROCESS_PRIVATE 的行為與局部條件變量相同。PTHREAD_PROCESS_SHARED 的行為與全局條件變量等效。
pthread_condattr_setpshared 返回值
pthread_condattr_setpshared() 在成功完成之后會返回零。其他任何返回值都表示出現了錯誤。如果出現以下情況,該函數將失敗并返回對應的值。
EINVAL
描述:
cattr 或 pshared 的值無效。
獲取條件變量的范圍
pthread_condattr_getpshared(3C) 可用來獲取屬性對象 cattr 的 pshared 的當前值。
pthread_condattr_getpshared 語法
int pthread_condattr_getpshared(const pthread_condattr_t *cattr,
int *pshared);
#include <pthread.h>
pthread_condattr_t cattr;
int pshared;
int ret;
/* get pshared value of condition variable */
ret = pthread_condattr_getpshared(&cattr, &pshared);
屬性對象的值為 PTHREAD_PROCESS_SHARED 或 PTHREAD_PROCESS_PRIVATE。
pthread_condattr_getpshared 返回值
pthread_condattr_getpshared() 在成功完成之后會返回零。其他任何返回值都表示出現了錯誤。如果出現以下情況,該函數將失敗并返回對應的值。
EINVAL
描述:
cattr 的值無效。
本節介紹如何使用條件變量。表 4–6 列出了可用的函數。
表 4–6 條件變量函數
初始化條件變量
使用 pthread_cond_init(3C) 可以將 cv 所指示的條件變量初始化為其缺省值,或者指定已經使用 pthread_condattr_init() 設置的條件變量屬性。
pthread_cond_init 語法
int pthread_cond_init(pthread_cond_t *cv,
const pthread_condattr_t *cattr);
#include <pthread.h>
pthread_cond_t cv;
pthread_condattr_t cattr;
int ret;
/* initialize a condition variable to its default value */
ret = pthread_cond_init(&cv, NULL);
/* initialize a condition variable */
ret = pthread_cond_init(&cv, &cattr);
cattr 設置為 NULL。將 cattr 設置為 NULL 與傳遞缺省條件變量屬性對象的地址等效,但是沒有內存開銷。對于 Solaris 線程,請參見cond_init 語法。
使用 PTHREAD_COND_INITIALIZER 宏可以將以靜態方式定義的條件變量初始化為其缺省屬性。PTHREAD_COND_INITIALIZER 宏與動態分配具有 null 屬性的 pthread_cond_init() 等效,但是不進行錯誤檢查。
多個線程決不能同時初始化或重新初始化同一個條件變量。如果要重新初始化或銷毀某個條件變量,則應用程序必須確保該條件變量未被使用。
pthread_cond_init 返回值
pthread_cond_init() 在成功完成之后會返回零。其他任何返回值都表示出現了錯誤。如果出現以下任一情況,該函數將失敗并返回對應的值。
EINVAL
描述:
cattr 指定的值無效。
EBUSY
描述:
條件變量處于使用狀態。
EAGAIN
描述:
必要的資源不可用。
ENOMEM
描述:
內存不足,無法初始化條件變量。
基于條件變量阻塞
使用 pthread_cond_wait(3C) 可以以原子方式釋放 mp 所指向的互斥鎖,并導致調用線程基于 cv 所指向的條件變量阻塞。對于 Solaris 線程,請參見cond_wait 語法。
pthread_cond_wait 語法
int pthread_cond_wait(pthread_cond_t *cv,pthread_mutex_t *mutex);
#include <pthread.h>
pthread_cond_t cv;
pthread_mutex_t mp;
int ret;
/* wait on condition variable */
ret = pthread_cond_wait(&cv, &mp);
阻塞的線程可以通過 pthread_cond_signal() 或 pthread_cond_broadcast() 喚醒,也可以在信號傳送將其中斷時喚醒。
不能通過 pthread_cond_wait() 的返回值來推斷與條件變量相關聯的條件的值的任何變化。必須重新評估此類條件。
pthread_cond_wait() 例程每次返回結果時調用線程都會鎖定并且擁有互斥鎖,即使返回錯誤時也是如此。
該條件獲得信號之前,該函數一直被阻塞。該函數會在被阻塞之前以原子方式釋放相關的互斥鎖,并在返回之前以原子方式再次獲取該互斥鎖。
通常,對條件表達式的評估是在互斥鎖的保護下進行的。如果條件表達式為假,線程會基于條件變量阻塞。然后,當該線程更改條件值時,另一個線程會針對條件變量發出信號。這種變化會導致所有等待該條件的線程解除阻塞并嘗試再次獲取互斥鎖。
必須重新測試導致等待的條件,然后才能從 pthread_cond_wait() 處繼續執行。喚醒的線程重新獲取互斥鎖并從 pthread_cond_wait() 返回之前,條件可能會發生變化。等待線程可能并未真正喚醒。建議使用的測試方法是,將條件檢查編寫為調用 pthread_cond_wait() 的 while() 循環。
pthread_mutex_lock();
while(condition_is_false)
pthread_cond_wait();
pthread_mutex_unlock();
如果有多個線程基于該條件變量阻塞,則無法保證按特定的順序獲取互斥鎖。
注 –
pthread_cond_wait() 是取消點。如果取消處于暫掛狀態,并且調用線程啟用了取消功能,則該線程會終止,并在繼續持有該鎖的情況下開始執行清除處理程序。
pthread_cond_wait 返回值
pthread_cond_wait() 在成功完成之后會返回零。其他任何返回值都表示出現了錯誤。如果出現以下情況,該函數將失敗并返回對應的值。
EINVAL
描述:
cv 或 mp 指定的值無效。
解除阻塞一個線程
對于基于 cv 所指向的條件變量阻塞的線程,使用 pthread_cond_signal(3C) 可以解除阻塞該線程。對于 Solaris 線程,請參見cond_signal 語法。
pthread_cond_signal 語法
int pthread_cond_signal(pthread_cond_t *cv);
#include <pthread.h>
pthread_cond_t cv;
int ret;
/* one condition variable is signaled */
ret = pthread_cond_signal(&cv);
應在互斥鎖的保護下修改相關條件,該互斥鎖用于獲得信號的條件變量中。否則,可能在條件變量的測試和 pthread_cond_wait() 阻塞之間修改該變量,這會導致無限期等待。
調度策略可確定喚醒阻塞線程的順序。對于 SCHED_OTHER,將按優先級順序喚醒線程。
如果沒有任何線程基于條件變量阻塞,則調用 pthread_cond_signal() 不起作用。
示例 4–8 使用 pthread_cond_wait() 和 pthread_cond_signal()
pthread_mutex_t count_lock;
pthread_cond_t count_nonzero;
unsigned count;
decrement_count()
{
pthread_mutex_lock(&count_lock);
while (count == 0)
pthread_cond_wait(&count_nonzero, &count_lock);
count = count - 1;
pthread_mutex_unlock(&count_lock);
}
increment_count()
{
pthread_mutex_lock(&count_lock);
if (count == 0)
pthread_cond_signal(&count_nonzero);
count = count + 1;
pthread_mutex_unlock(&count_lock);
}
pthread_cond_signal 返回值
pthread_cond_signal() 在成功完成之后會返回零。其他任何返回值都表示出現了錯誤。如果出現以下情況,該函數將失敗并返回對應的值。
EINVAL
描述:
cv 指向的地址非法。
示例 4–8 說明了如何使用 pthread_cond_wait() 和 pthread_cond_signal()。
在指定的時間之前阻塞
pthread_cond_timedwait(3C) 的用法與 pthread_cond_wait() 的用法基本相同,區別在于在由 abstime 指定的時間之后 pthread_cond_timedwait() 不再被阻塞。
pthread_cond_timedwait 語法
int pthread_cond_timedwait(pthread_cond_t *cv,
pthread_mutex_t *mp, const struct timespec *abstime);
#include <pthread.h>
#include <time.h>
pthread_cond_t cv;
pthread_mutex_t mp;
timestruct_t abstime;
int ret;
/* wait on condition variable */
ret = pthread_cond_timedwait(&cv, &mp, &abstime);
pthread_cond_timewait() 每次返回時調用線程都會鎖定并且擁有互斥鎖,即使 pthread_cond_timedwait() 返回錯誤時也是如此。 對于 Solaris 線程,請參見cond_timedwait 語法。
pthread_cond_timedwait() 函數會一直阻塞,直到該條件獲得信號,或者最后一個參數所指定的時間已過為止。
注 –
pthread_cond_timedwait() 也是取消點。
示例 4–9 計時條件等待
pthread_timestruc_t to;
pthread_mutex_t m;
pthread_cond_t c;
...
pthread_mutex_lock(&m);
to.tv_sec = time(NULL) + TIMEOUT;
to.tv_nsec = 0;
while (cond == FALSE) {
err = pthread_cond_timedwait(&c, &m, &to);
if (err == ETIMEDOUT) {
/* timeout, do something */
break;
}
}
pthread_mutex_unlock(&m);
pthread_cond_timedwait 返回值
pthread_cond_timedwait() 在成功完成之后會返回零。其他任何返回值都表示出現了錯誤。如果出現以下任一情況,該函數將失敗并返回對應的值。
EINVAL
描述:
cv 或 abstime 指向的地址非法。
ETIMEDOUT
描述:
abstime 指定的時間已過。
超時會指定為當天時間,以便在不重新計算值的情況下高效地重新測試條件,如示例 4–9 中所示。
在指定的時間間隔內阻塞
pthread_cond_reltimedwait_np(3C) 的用法與 pthread_cond_timedwait() 的用法基本相同,唯一的區別在于 pthread_cond_reltimedwait_np() 會采用相對時間間隔而不是將來的絕對時間作為其最后一個參數的值。
pthread_cond_reltimedwait_np 語法
int pthread_cond_reltimedwait_np(pthread_cond_t *cv,
pthread_mutex_t *mp,
const struct timespec *reltime);
#include <pthread.h>
#include <time.h>
pthread_cond_t cv;
pthread_mutex_t mp;
timestruct_t reltime;
int ret;
/* wait on condition variable */
ret = pthread_cond_reltimedwait_np(&cv, &mp, &reltime);
pthread_cond_reltimedwait_np() 每次返回時調用線程都會鎖定并且擁有互斥鎖,即使 pthread_cond_reltimedwait_np() 返回錯誤時也是如此。對于 Solaris 線程,請參見 cond_reltimedwait(3C)。pthread_cond_reltimedwait_np() 函數會一直阻塞,直到該條件獲得信號,或者最后一個參數指定的時間間隔已過為止。
注 –
pthread_cond_reltimedwait_np() 也是取消點。
pthread_cond_reltimedwait_np 返回值
pthread_cond_reltimedwait_np() 在成功完成之后會返回零。其他任何返回值都表示出現了錯誤。如果出現以下任一情況,該函數將失敗并返回對應的值。
EINVAL
描述:
cv 或 reltime 指示的地址非法。
ETIMEDOUT
描述:
reltime 指定的時間間隔已過。
解除阻塞所有線程
對于基于 cv 所指向的條件變量阻塞的線程,使用 pthread_cond_broadcast(3C) 可以解除阻塞所有這些線程,這由 pthread_cond_wait() 來指定。
pthread_cond_broadcast 語法
int pthread_cond_broadcast(pthread_cond_t *cv);
#include <pthread.h>
pthread_cond_t cv;
int ret;
/* all condition variables are signaled */
ret = pthread_cond_broadcast(&cv);
如果沒有任何線程基于該條件變量阻塞,則調用 pthread_cond_broadcast() 不起作用。對于 Solaris 線程,請參見cond_broadcast 語法。
由于 pthread_cond_broadcast() 會導致所有基于該條件阻塞的線程再次爭用互斥鎖,因此請謹慎使用 pthread_cond_broadcast()。例如,通過使用 pthread_cond_broadcast(),線程可在資源釋放后爭用不同的資源量,如示例 4–10 中所示。
示例 4–10 條件變量廣播
pthread_mutex_t rsrc_lock;
pthread_cond_t rsrc_add;
unsigned int resources;
get_resources(int amount)
{
pthread_mutex_lock(&rsrc_lock);
while (resources < amount) {
pthread_cond_wait(&rsrc_add, &rsrc_lock);
}
resources -= amount;
pthread_mutex_unlock(&rsrc_lock);
}
add_resources(int amount)
{
pthread_mutex_lock(&rsrc_lock);
resources += amount;
pthread_cond_broadcast(&rsrc_add);
pthread_mutex_unlock(&rsrc_lock);
}
請注意,在 add_resources() 中,首先更新 resources 還是首先在互斥鎖中調用 pthread_cond_broadcast() 無關緊要。
應在互斥鎖的保護下修改相關條件,該互斥鎖用于獲得信號的條件變量中。否則,可能在條件變量的測試和 pthread_cond_wait() 阻塞之間修改該變量,這會導致無限期等待。
pthread_cond_broadcast 返回值
pthread_cond_broadcast() 在成功完成之后會返回零。其他任何返回值都表示出現了錯誤。如果出現以下情況,該函數將失敗并返回對應的值。
EINVAL
描述:
cv 指示的地址非法。
銷毀條件變量狀態
使用 pthread_cond_destroy(3C) 可以銷毀與 cv 所指向的條件變量相關聯的任何狀態。對于 Solaris 線程,請參見cond_destroy 語法。
pthread_cond_destroy 語法
int pthread_cond_destroy(pthread_cond_t *cv);
#include <pthread.h>
pthread_cond_t cv;
int ret;
/* Condition variable is destroyed */
ret = pthread_cond_destroy(&cv);
請注意,沒有釋放用來存儲條件變量的空間。
pthread_cond_destroy 返回值
pthread_cond_destroy() 在成功完成之后會返回零。其他任何返回值都表示出現了錯誤。如果出現以下情況,該函數將失敗并返回對應的值。
EINVAL
描述:
cv 指定的值無效。
喚醒丟失問題
如果線程未持有與條件相關聯的互斥鎖,則調用 pthread_cond_signal() 或 pthread_cond_broadcast() 會產生喚醒丟失錯誤。
滿足以下所有條件時,即會出現喚醒丟失問題:
僅當修改所測試的條件但未持有與之相關聯的互斥鎖時,才會出現此問題。只要僅在持有關聯的互斥鎖同時修改所測試的條件,即可調用 pthread_cond_signal() 和 pthread_cond_broadcast(),而無論這些函數是否持有關聯的互斥鎖。
生成方和使用者問題
并發編程中收集了許多標準的眾所周知的問題,生成方和使用者問題只是其中的一個問題。此問題涉及到一個大小限定的緩沖區和兩類線程(生成方和使用者),生成方將項放入緩沖區中,然后使用者從緩沖區中取走項。
生成方必須在緩沖區中有可用空間之后才能向其中放置內容。使用者必須在生成方向緩沖區中寫入之后才能從中提取內容。
條件變量表示一個等待某個條件獲得信號的線程隊列。
示例 4–11 中包含兩個此類隊列。一個隊列 (less) 針對生成方,用于等待緩沖區中出現空位置。另一個隊列 (more) 針對使用者,用于等待從緩沖槽位的空位置中提取其中包含的信息。該示例中還包含一個互斥鎖,因為描述該緩沖區的數據結構一次只能由一個線程訪問。
示例 4–11 生成方和使用者的條件變量問題
typedef struct {
char buf[BSIZE];
int occupied;
int nextin;
int nextout;
pthread_mutex_t mutex;
pthread_cond_t more;
pthread_cond_t less;
} buffer_t;
buffer_t buffer;
如示例 4–12 中所示,生成方線程獲取該互斥鎖以保護 buffer 數據結構,然后,緩沖區確定是否有空間可用于存放所生成的項。如果沒有可用空間,生成方線程會調用 pthread_cond_wait()。pthread_cond_wait() 會導致生成方線程連接正在等待 less 條件獲得信號的線程隊列。less 表示緩沖區中的可用空間。
與此同時,在調用 pthread_cond_wait() 的過程中,該線程會釋放互斥鎖的鎖定。正在等待的生成方線程依賴于使用者線程在條件為真時發出信號,如示例 4–12 中所示。該條件獲得信號時,將會喚醒等待 less 的第一個線程。但是,該線程必須再次鎖定互斥鎖,然后才能從 pthread_cond_wait() 返回。
獲取互斥鎖可確保該線程再次以獨占方式訪問緩沖區的數據結構。該線程隨后必須檢查緩沖區中是否確實存在可用空間。如果空間可用,該線程會向下一個可用的空位置中進行寫入。
與此同時,使用者線程可能正在等待項出現在緩沖區中。這些線程正在等待條件變量 more。剛在緩沖區中存儲內容的生成方線程會調用 pthread_cond_signal() 以喚醒下一個正在等待的使用者。如果沒有正在等待的使用者,此調用將不起作用。
最后,生成方線程會解除鎖定互斥鎖,從而允許其他線程處理緩沖區的數據結構。
示例 4–12 生成方和使用者問題:生成方
void producer(buffer_t *b, char item)
{
pthread_mutex_lock(&b->mutex);
while (b->occupied >= BSIZE)
pthread_cond_wait(&b->less, &b->mutex);
assert(b->occupied < BSIZE);
b->buf[b->nextin++] = item;
b->nextin %= BSIZE;
b->occupied++;
/* now: either b->occupied < BSIZE and b->nextin is the index
of the next empty slot in the buffer, or
b->occupied == BSIZE and b->nextin is the index of the
next (occupied) slot that will be emptied by a consumer
(such as b->nextin == b->nextout) */
pthread_cond_signal(&b->more);
pthread_mutex_unlock(&b->mutex);
}
請注意 assert() 語句的用法。除非在編譯代碼時定義了 NDEBUG,否則 assert() 在其參數的計算結果為真(非零)時將不執行任何操作。如果參數的計算結果為假(零),則該程序會中止。在多線程程序中,此類斷言特別有用。如果斷言失敗,assert() 會立即指出運行時問題。assert() 還有另一個作用,即提供有用的注釋。
以 /* now: either b->occupied ... 開頭的注釋最好以斷言形式表示,但是由于語句過于復雜,無法用布爾值表達式來表示,因此將用英語表示。
斷言和注釋都是不變量的示例。這些不變量是邏輯語句,在程序正常執行時不應將其聲明為假,除非是線程正在修改不變量中提到的一些程序變量時的短暫修改過程中。當然,只要有線程執行語句,斷言就應當為真。
使用不變量是一種極為有用的方法。即使沒有在程序文本中聲明不變量,在分析程序時也應將其視為不變量。
每次線程執行包含注釋的代碼時,生成方代碼中表示為注釋的不變量始終為真。如果將此注釋移到緊挨 mutex_unlock() 的后面,則注釋不一定仍然為真。如果將此注釋移到緊跟 assert() 之后的位置,則注釋仍然為真。
因此,不變量可用于表示一個始終為真的屬性,除非一個生成方或一個使用者正在更改緩沖區的狀態。線程在互斥鎖的保護下處理緩沖區時,該線程可能會暫時聲明不變量為假。但是,一旦線程結束對緩沖區的操作,不變量即會恢復為真。
示例 4–13 給出了使用者的代碼。該邏輯流程與生成方的邏輯流程相對稱。
示例 4–13 生成方和使用者問題:使用者
char consumer(buffer_t *b)
{
char item;
pthread_mutex_lock(&b->mutex);
while(b->occupied <= 0)
pthread_cond_wait(&b->more, &b->mutex);
assert(b->occupied > 0);
item = b->buf[b->nextout++];
b->nextout %= BSIZE;
b->occupied--;
/* now: either b->occupied > 0 and b->nextout is the index
of the next occupied slot in the buffer, or
b->occupied == 0 and b->nextout is the index of the next
(empty) slot that will be filled by a producer (such as
b->nextout == b->nextin) */
pthread_cond_signal(&b->less);
pthread_mutex_unlock(&b->mutex);
return(item);
}
PowerDesigner設置主鍵自增方法:選中主鍵字段,點擊進入屬性設置框,勾選"Identity",這里注意不同的SQL會有不同的方法,比如MySQL為:ATUO_INCREMENT,而SQL Server為:Identity,請選擇你需要的數據庫平臺。更換平臺方法:Tool-->Generate Physical Data Mode--> General(默認就會打開這里)-->DBMS里選擇你的數據庫平臺即可。。。
sql語句中表名與字段名前的引號去除:
打開cdm的情況下,進入Tools-Model Options-Naming Convention,把Name和Code的標簽的Charcter case選項設置成Uppercase或者Lowercase,只要不是Mixed Case就行!
或者選擇Database->Edit current database->Script->Sql->Format,有一項CaseSensitivityUsingQuote,它的 comment為“Determines if the case sensitivity for identifiers is managed using double quotes”,表示是否適用雙引號來規定標識符的大小寫, 可以看到右邊的values默認值為“YES”,改為“No”即可!
或者在打開pdm的情況下,進入Tools-Model Options-Naming Convention,把Name和Code的標簽的Charcter case選項設置成Uppercase就可以!
在修改name的時候,code的值將跟著變動,很不方便修改方法:PowerDesign中的選項菜單里修改,在[Tool]-->[General Options]->[Dialog]->[Operating modes]->[Name to Code mirroring],這里默認是讓名稱和代碼同步,將前面的復選框去掉就行了。
由pdm生成建表腳本時,字段超過15字符就發生錯誤(oracle) 原因未知,解決辦法是打開PDM后,會出現Database的菜單欄,進入Database - Edit Current DBMS -script-objects-column-maxlen,把value值調大(原為30),比如改成60。出現表或者其它對象的長度也有這種錯誤的話都可以選擇對應的objects照此種方法更改!
或者使用下面的這種方法:
生成建表腳本時會彈出Database generation提示框:把options - check model的小勾給去掉,就是不進行檢查(不推薦)!
或者可以修改C:\Program Files\Sybase\PowerDesigner Trial 11\Resource Files\DBMS\oracl9i2.xdb文件
修改好后,再cdm轉為pdm時,選擇“Copy the DBMS definition in model”把把這個資源文件拷貝到模型中。
由CDM生成PDM時,自動生成的外鍵的重命名PDM Generation Options->Detail->FK index names默認是%REFR%_FK,改為FK_%REFRCODE%,其中%REFRCODE%指的就是CDM中Relationship的code!另外自動生成的父字段的規則是PDM Generation Options->Detail->FK column name template中設置的,默認是%.3:PARENT%_%COLUMN%,可以改為Par%COLUMN%表示是父字段!
建立一個表后,為何檢測出現Existence of index的警告 A table should contain at least one column, one index, one key, and one reference.
可以不檢查 Existence of index 這項,也就沒有這個警告錯誤了!
意思是說沒有給表建立索引,而一個表一般至少要有一個索引,這是一個警告,不用管也沒有關系!
如何防止一對一的關系生成兩個引用(外鍵)要定義關系的支配方向,占支配地位的實體(有D標志)變為父表。
在cdm中雙擊一對一關系->Detail->Dominant role選擇支配關系
修改報表模板中一些術語的定義即文件:C:\Program Files\Sybase\PowerDesigner Trial 11\Resource Files\Report Languages\Chinese.xrl
Tools-Resources-Report Languages-選擇Chinese-單擊Properties或雙擊目標
修改某些對象的名稱:Object Attributes\Physical Data Model\Column\
ForeignKey:外鍵
Mandatory:為空
Primary:主鍵
Table:表
用查找替換,把“表格”替換成“表”修改顯示的內容為別的:Values Mapping\Lists\Standard,添加TRUE的轉化列為是,FALSE的轉化列為空
另外Report-Title Page里可以設置標題信息
PowerDesigner11中批量根據對象的name生成comment的腳本'******************************************************************************
'* File: name2comment.vbs
'* Purpose: Database generation cannot use object names anymore
' in version 7 and above.
' It always uses the object codes.
'
' In case the object codes are not aligned with your
' object names in your model, this script will copy
' the object Name onto the object comment for
' the Tables and Columns.
'
'* Title: 把對象name拷入comment屬性中
'* Version: 1.0
'* Author:
'* 執行方法:PD11 -- Open PDM -- Tools -- Execute Commands -- Run Script
'******************************************************************************
Option Explicit
ValidationMode = True
InteractiveMode = im_Batch
Dim mdl ' the current model
' get the current active model
Set mdl = ActiveModel
If (mdl Is Nothing) Then
MsgBox "There is no current Model"
ElseIf Not mdl.IsKindOf(PdPDM.cls_Model) Then
MsgBox "The current model is not an Physical Data model."
Else
ProcessFolder mdl
End If
' This routine copy name into code for each table, each column and each view
' of the current folder
Private sub ProcessFolder(folder)
Dim Tab 'running table
for each Tab in folder.tables
if not tab.isShortcut then
tab.comment = tab.name
Dim col ' running column
for each col in tab.columns
col.comment= col.name
next
end if
next
Dim view 'running view
for each view in folder.Views
if not view.isShortcut then
view.comment = view.name
end if
next
' go into the sub-packages
Dim f ' running folder
For Each f In folder.Packages
if not f.IsShortcut then
ProcessFolder f
end if
Next
end sub
PowerDesigner 生成SQL的Existence of refernce錯誤問題現象:用PowerDesigner生成SQL語句時,提示Existence of refernce錯誤。
原因:該表沒有與其他表的關聯(如外鍵等),而PowerDesigner需要存在一個refernce才能生成SQL.
解決方法:
在工具欄空白處右鍵打開Palette面板,選中Link/Extended Dependency 按鈕,然后在提示出錯的表上添加到自己的Dependency。
重新生成SQL,你將發現剛才提示的錯誤沒有了,問題解決。
利用PowerDesigner批量生成測試數據主要解決方法:
A:在PowerDesigner 建表
B:然后給每一個表的字段建立相應的摘要文件
步驟如下:
Model->Test Data Profiles配置每一個字段摘要文件General:輸入Name、Code、
選擇Class(數字、字符、時間)類型
選擇Generation Source: Automatic、List、ODBC、File Detail:配置字段相關信息
所有字段摘要文件配置完成后雙擊該表->選擇字段->Detail->選擇Test Data Parameters 摘要文件如果字段值與其它字段有關系在: Computed Expression 中輸入計算列--生成測試數據:
DataBase->Generation Test Data->
選擇:Genration 類型(Sript、ODBC)
Selection(選擇要生成的表)
Test Data Genration(Default number of rows 生成記錄行數)
在使用PowerDesigner的過程中,經常遇到一些設置上面的問題,每次都去找老鳥幫忙解決,隔一段時間不用,下一次又忘掉了,不好意思再去麻煩他們了,所以現在用博客園記錄下來,以后上園子來找以前的東西.
1
取消Name和Code關聯的設置
在設計PDM文件的時候,設計一張表,在填寫欄位的時候,如果我們輸入Name,Code會跟著變化.這個完全是西方人的習慣,因為他們的Name和Code都是E文,所以不會出現什么問題.但是,我們使用的時候,就會很不習慣,Name應該是中文名字,Code才是資料庫的實際字段名.
下面記錄修改設置的步驟:
Step 1:
菜單欄找到Tools,點開,找到General Options,點擊
Step 2:打開Dialog將Operating modes中的 Name To Code mirroring 將前面的勾去掉

OK!完成
摘要:
··· 2
第一節 全文檢索系統與Lucene簡介··· 3
一、 什么是全文檢索與全文檢索系統?··· 3
二、 什么是Lucene?&...
閱讀全文
一、Lucene源碼實現分析的說明
通過以上對Lucene系統結構的分析,我們已經大致的清楚了Lucene 系統的組成,以及在Lucene系統之上的開發步驟。接下來,我們試圖來分析Lucene項目(采用Lucene 1.2版本)的源碼實現,考察其實現的細節。這不僅僅是我們嘗試用C++語言重新實現Lucene的必須工作,也是進一步做Lucene開發工作的必要準備。因此,這一部分所涉及到的內容,對于Lucene上的應用開發也是有價值的,尤其是本部分所做的文件格式分析。
由于本文建立在我們的畢設項目之上,且同時我們需要實現cLucene項目,因此很遺憾的我們并沒有完全的完成Lucene的所有源碼實現的分析工作。接下來的部分,我們將涉及的部分為Lucene文件格式分析,Lucene中的存儲抽象模塊分析,以及Lucene中的索引構建邏輯模塊分析。這一部分,我們主要涉及到的是文件格式分析與存儲抽象模塊分析。
二、Lucene索引文件格式
在Lucene的web站點上,有關于 Lucene的文件格式的規范,其規定了Lucene的文件格式采取的存儲單位、組織結構、命名規范等等內容,但是它僅僅是一個規范說明,并沒有從實現者角度來衡量這個規范的實現。因此,我們以下的內容,結合了我們自己的分析與文件格式的定義規范,以期望給出一個更加清晰的文件格式說明。具體的文檔規范可以參考后面的文獻2。
首先在Lucene的文件格式中,以字節為基礎,定義了如下的數據類型:
表 3.1 Lucene文件格式中定義的數據類型
數據類型 |
所占字節長度(字節) |
說明 |
Byte |
1 |
基本數據類型,其他數據類型以此為基礎定義 |
UInt32 |
4 |
32位無符號整數,高位優先 |
UInt64 |
8 |
64位無符號整數,高位優先 |
VInt |
不定,最少1字節 |
動態長度整數,每字節的最高位表明還剩多少字節,每字節的低七位表明整數的值,高位優先。可以認為值可以為無限大。其示例如下
值 |
字節1 |
字節2 |
字節3 |
0 |
00000000 |
|
|
1 |
00000001 |
|
|
2 |
00000010 |
|
|
127 |
01111111 |
|
|
128 |
10000000 |
00000001 |
|
129 |
10000001 |
00000001 |
|
130 |
10000010 |
00000001 |
|
16383 |
10000000 |
10000000 |
00000001 |
16384 |
10000001 |
10000000 |
00000001 |
16385 |
10000010 |
10000000 |
00000001 |
|
Chars |
不定,最少1字節 |
采用UTF-8編碼[20]的Unicode字符序列 |
String |
不定,最少2字節 |
由VInt和Chars組成的字符串類型,VInt表示Chars的長度,Chars則表示了String的值 |
以上的數據類型就是Lucene索引文件格式中用到的全部數據類型,由于它們都以字節為基礎定義而來,因此保證了是平臺無關,這也是Lucene索引文件格式平臺無關的主要原因。接下來我們看看Lucene索引文件的概念組成和結構組成。

以上就是Lucene的索引文件的概念結構。Lucene索引index由若干段(segment)組成,每
一段由若干的文檔(document)組成,每一個文檔由若干的域(field)組成,每一個域由若干的項(term)組成。項是最小的索引概念單位,它直接代表了一個字符串以及其在文件中的位置、出現次數等信息。域是一個關聯的元組,由一個域名和一個域值組成,域名是一個字串,域值是一個項,比如將“標題”和實際標題的項組成的域。文檔是提取了某個文件中的所有信息之后的結果,這些組成了段,或者稱為一個子索引。子索引可以組合為索引,也可以合并為一個新的包含了所有合并項內部元素的子索引。我們可以清楚的看出,Lucene的索引結構在概念上即為傳統的倒排索引結構[21]。
從概念上映射到結構中,索引被處理為一個目錄(文件夾),其中含有的所有文件即為其內容,這些文件按照所屬的段不同分組存放,同組的文件擁有相同的文件名,不同的擴展名。此外還有三個文件,分別用來保存所有的段的記錄、保存已刪除文件的記錄和控制讀寫的同步,它們分別是segments, deletable和lock文件,都沒有擴展名。每個段包含一組文件,它們的文件擴展名不同,但是文件名均為記錄在文件segments中段的名字。讓我們看如下的結構圖3.2。
關于圖3.2中的各個文件具體的內部格式,在參考文獻3中,均可以找到詳細的說明。接下來我們從宏觀關系上說明一下這些文件組成。在這些宏觀上的關系理清楚之后,仔細閱讀參考文獻3,即可清楚的明白具體的Lucene文件格式。
每個段的文件中,主要記錄了兩大類的信息:域集合與項集合。這兩個集合中所含有的文件在圖3.2中均有表明。由于索引信息是靜態存儲的,域集合與項集合中的文件組采用了一種類似的存儲辦法:一個小型的索引文件,運行時載入內存;一個對應于索引文件的實際信息文件,可以按照索引中指示的偏移量隨機訪問;索引文件與信息文件在記錄的排列順序上存在隱式的對應關系,即索引文件中按照“索引項1、索引項2…”排列,則信息文件則也按照“信息項1、信息項2…”排列。比如在圖3.2所示文件中,segment1.fdx與segment1.fdt之間,segment1.tii與segment1.tis、 segment1.prx、segment1.frq之間,都存在這樣的組織關系。而域集合與項集合之間則通過域的在域記錄文件(比如 segment1.fnm)中所記錄的域記錄號維持對應關系,在圖3.2中segment1.fdx與segment1.tii中就是通過這種方式保持聯系。這樣,域集合和項集合不僅僅聯系起來,而且其中的文件之間也相互聯系起來。此外,標準化因子文件和被刪除文檔文件則提供了一些程序內部的輔助設施(標準化因子用在評分排序機制中,被刪除文檔是一種偽刪除手段)。這樣,整個段的索引信息就通過這些文檔有機的組成。
以上所闡述的,就是 Lucene所采用的索引文件格式。基本上而言,它是一個倒排索引,但是Lucene在文件的安排上做了一些努力,比如使用索引/信息文件的方式,從文件安排的形式上提高查找的效率。這是一種數據庫之外的處理方法,其有其優點(格式平**立、速度快),也有其缺點(獨立性帶來的共享訪問接口問題等等),具體如何衡量兩種方法之間的利弊,本文這里就不討論了。
三、一些公用的基礎類
分析完索引文件格式,我們接下來應該著手對存儲抽象也就是org.apache.lucenestore中的源碼做一些分析。我們先不著急分析這部分,而是分析圖2.1中基礎結構封裝那一部分,因為這是整個系統的基石,然后我們在下一部分再來分析存儲抽象。
基礎結構封裝,或者基礎類,由org.apache.lucene.util和org.apache.lucene.document兩個包組成,前者定義了一些常量和優化過的常用的數據結構和算法,后者則是對于文檔(document)和域(field)概念的一個類定義。以下我們用列表的方式來分析這些封裝類,指出其要點。
表 3.2 基礎類包org.apache.lucene.util
類 |
說明 |
Arrays |
一個關于數組的排序方法的靜態類,提供了優化的基于快排序的排序方法sort |
BitVector |
C/C++語言中位域的java實現品,但是加入了序列化能力 |
Constants |
常量靜態類,定義了一些常量 |
PriorityQueue |
一個優先隊列的抽象類,用于后面實現各種具體的優先隊列,提供常數時間內的最小元素訪問能力,內部實現機制是哈析表和堆排序算法 |
表 3.3 基礎類包org.apache.lucene.document
類 |
說明 |
Document |
是文檔概念的一個實現類,每個文檔包含了一個域表(fieldList),并提供了一些實用的方法,比如多種添加域的方法、返回域表的迭代器的方法 |
Field |
是域概念的一個實現類,每個域包含了一個域名和一個值,以及一些相關的屬性 |
DateField |
提供了一些輔助方法的靜態類,這些方法將java中Date和Time數據類型和String相互轉化 |
總的來說,這兩個基礎類包中含有的類都比較簡單,通過閱讀源代碼,可以很容易的理解,因此這里不作過多的展開。
四、存儲抽象
有了上面的知識,我們接下來來分析存儲抽象部分,也就是org.apache.lucene.store包。存儲抽象是唯一能夠直接對索引文件存取的包,因此其主要目的是抽象出和平臺文件系統無關的存儲抽象,提供諸如目錄服務(增、刪文件)、輸入流和輸出流。在分析其實現之前,首先我們看一下UML [22]圖。

圖 3.3 存儲抽象實現UML圖(一)

圖 3.4 存儲抽象實現UML圖(二)

圖 3.4 存儲抽象實現UML圖(三)
圖3.2到3.4展示了整個org.apache.lucene.store中主要的繼承體系。共有三個抽象類定義:Directory、 InputStream和OutputStrem,構成了一個完整的基于抽象文件系統的存取體系結構,在此基礎上,實作出了兩個實現品:(FSDirectory,FSInputStream,FSOutputStream)和(RAMDirectory,RAMInputStream和 RAMOutputStream)。前者是以實際的文件系統做為基礎實現的,后者則是建立在內存中的虛擬文件系統。前者主要用來永久的保存索引文件,后者的作用則在于索引操作時是在內存中建立小的索引,然后一次性的輸出合并到文件中去,這一點我們在后面的索引邏輯部分能夠看到。此外,還定以了 org.apache.lucene.store.lock和org.apache.lucene.store.with兩個輔助內部實現的類用在實現 Directory方法的makeLock的時候,以在鎖定索引讀寫之前來讓客戶程序做一些準備工作。
(FSDirectory, FSInputStream,FSOutputStream)的內部實現依托于java語言中的io類庫,只是簡單的做了一個外部邏輯的包裝。這當然要歸功于java語言所提供的跨平臺特性,同時也帶了一些隱患:文件存取的效率提升需要依耐于文件類庫的優化。如果需要繼續優化文件存取的效率,應該還提供一個文件與目錄的抽象,以根據各種文件系統或者文件類型來提供一個優化的機會。當然,這是應用開發者所不需要關系的問題。
(RAMDirectory,RAMInputStream和RAMOutputStream)的內部實現就比較直接了,直接采用了虛擬的文件 RAMFile類(定義于文件RAMDirectory.java中)來表示文件,目錄則看作一個String與RAMFile對應的關聯數組。 RAMFile中采用數組來表示文件的存儲空間。在此的基礎上,完成各項操作的實現,就形成了基于內存的虛擬文件系統。因為在實際使用時,并不會牽涉到很大字節數量的文件,因此這種設計是簡單直接的,也是高效率的。
這部分的實現在理清楚繼承體系后,相當的簡單。因此接下來的部分,我們可以通過直接閱讀源代碼解決。接下來我們看看這個部分的源代碼如何在實際中使用的。
一般來說,我們使用的是抽象類提供的接口而不是實際的實現類本身。在實現類中一般都含有幾個靜態函數,比如createFile,它能夠返回一個 OutputStream接口,或者openFile,它能夠返回一個InputStream接口,利用這些接口之中的方法,比如 writeString,writeByte等等,我們就能夠在抽象的層次上處理Lucene定義的數據類型的讀寫。簡單的說,Lucene中存儲抽象這部分設計時采用了工廠模式(Factory parttern)[23]。我們利用靜態類的方法也就是工廠來創建對象,返回接口,通過接口來執行操作。
五、關于cLucene項目
這一部分詳細的說明了Lucene系統中所采用的索引文件格式、一些基礎類和存儲抽象。接下來我們來敘述一下我們在項目cLucene中重新實現這些結構時候的一些考慮。
cLucene徹底的遵守了Lucene所定義的索引文件格式,這是Lucene對于各個兼容系統的基本要求。在此基礎上,cLucene系統和Lucene系統才能夠共享索引文件數據。或者說,cLucene生成的索引文件和Lucene生成的索引文件完全等價。
在基礎類問題上,cLucene同樣封裝了類似的結構。我們同樣列表描述,請和前面的表3.2與3.3對照比較。
表 3.4 基礎類包cLucene::util
類 |
說明 |
Arrays |
沒有實現,直接利用了STL庫中的快排序算法實現 |
BitVector |
C/C++語言版本的實現,與java實現版本類似 |
Constants |
常量靜態類,定義了一些常量,但是與java版本不同的是,這里主要定義了一些宏 |
PriorityQueue |
這是一個類型定義,直接利用STL庫中的std::priority_queue |
表 3.3 基礎類包cLucene::document
類 |
說明 |
Document |
C/C++語言版本的實現,與java實現版本類似 |
Field |
C/C++語言版本的實現,與java實現版本類似 |
DateField |
沒有實現,直接利用OpenTop庫中的ot::StringUtil |
存儲抽象的實現上,也同樣是類似于java實現。由于我們采用了OpenTop庫,因此同樣得以借助其中對于文件系統抽象的ot::io包來解決文件系統問題。這部分問題與前面一樣,存在優化的可能。在實現的類層次上、對外接口上,均與java版本的一樣。
作者
Winter
一切復雜的排序操作,都可以通過STL方便實現 !
0 前言: STL,為什么你必須掌握
對于程序員來說,數據結構是必修的一門課。從查找到排序,從鏈表到二叉樹,幾乎所有的算法和原理都需要理解,理解不了也要死記硬背下來。幸運的是這些理論都已經比較成熟,算法也基本固定下來,不需要你再去花費心思去考慮其算法原理,也不用再去驗證其準確性。不過,等你開始應用計算機語言來工作的時候,你會發現,面對不同的需求你需要一次又一次去用代碼重復實現這些已經成熟的算法,而且會一次又一次陷入一些由于自己疏忽而產生的bug中。這時,你想找一種工具,已經幫你實現這些功能,你想怎么用就怎么用,同時不影響性能。你需要的就是STL, 標準模板庫!
西方有句諺語:不要重復發明輪子!
STL幾乎封裝了所有的數據結構中的算法,從鏈表到隊列,從向量到堆棧,對hash到二叉樹,從搜索到排序,從增加到刪除......可以說,如果你理解了STL,你會發現你已不用拘泥于算法本身,從而站在巨人的肩膀上去考慮更高級的應用。
排序是最廣泛的算法之一,本文詳細介紹了STL中不同排序算法的用法和區別。
1 STL提供的Sort 算法
C++之所以得到這么多人的喜歡,是因為它既具有面向對象的概念,又保持了C語言高效的特點。STL 排序算法同樣需要保持高效。因此,對于不同的需求,STL提供的不同的函數,不同的函數,實現的算法又不盡相同。
1.1 所有sort算法介紹
所有的sort算法的參數都需要輸入一個范圍,[begin, end)。這里使用的迭代器(iterator)都需是隨機迭代器(RadomAccessIterator), 也就是說可以隨機訪問的迭代器,如:it+n什么的。(partition 和stable_partition 除外)
如果你需要自己定義比較函數,你可以把你定義好的仿函數(functor)作為參數傳入。每種算法都支持傳入比較函數。以下是所有STL sort算法函數的名字列表:
函數名 |
功能描述 |
sort |
對給定區間所有元素進行排序 |
stable_sort |
對給定區間所有元素進行穩定排序 |
partial_sort |
對給定區間所有元素部分排序 |
partial_sort_copy |
對給定區間復制并排序 |
nth_element |
找出給定區間的某個位置對應的元素 |
is_sorted |
判斷一個區間是否已經排好序 |
partition |
使得符合某個條件的元素放在前面 |
stable_partition |
相對穩定的使得符合某個條件的元素放在前面 |
其中nth_element 是最不易理解的,實際上,這個函數是用來找出第幾個。例如:找出包含7個元素的數組中排在中間那個數的值,此時,我可能不關心前面,也不關心后面,我只關心排在第四位的元素值是多少。
1.2 sort 中的比較函數
當你需要按照某種特定方式進行排序時,你需要給sort指定比較函數,否則程序會自動提供給你一個比較函數。
vector < int > vect;
//...
sort(vect.begin(), vect.end());
//此時相當于調用
sort(vect.begin(), vect.end(), less<int>() );
上述例子中系統自己為sort提供了less仿函數。在STL中還提供了其他仿函數,以下是仿函數列表:
名稱 |
功能描述 |
equal_to |
相等 |
not_equal_to |
不相等 |
less |
小于 |
greater |
大于 |
less_equal |
小于等于 |
greater_equal |
大于等于 |
需要注意的是,這些函數不是都能適用于你的sort算法,如何選擇,決定于你的應用。另外,不能直接寫入仿函數的名字,而是要寫其重載的()函數:
less<int>()
greater<int>()
當你的容器中元素時一些標準類型(int float char)或者string時,你可以直接使用這些函數模板。但如果你時自己定義的類型或者你需要按照其他方式排序,你可以有兩種方法來達到效果:一種是自己寫比較函數。另一種是重載類型的'<'操作賦。
#include <iostream>
#include <algorithm>
#include <functional>
#include <vector>
using namespace std;
class myclass {
public:
myclass(int a, int b):first(a), second(b){}
int first;
int second;
bool operator < (const myclass &m)const {
return first < m.first;
}
};
bool less_second(const myclass & m1, const myclass & m2) {
return m1.second < m2.second;
}
int main() {
vector< myclass > vect;
for(int i = 0 ; i < 10 ; i ++){
myclass my(10-i, i*3);
vect.push_back(my);
}
for(int i = 0 ; i < vect.size(); i ++)
cout<<"("<<vect[i].first<<","<<vect[i].second<<")\n";
sort(vect.begin(), vect.end());
cout<<"after sorted by first:"<<endl;
for(int i = 0 ; i < vect.size(); i ++)
cout<<"("<<vect[i].first<<","<<vect[i].second<<")\n";
cout<<"after sorted by second:"<<endl;
sort(vect.begin(), vect.end(), less_second);
for(int i = 0 ; i < vect.size(); i ++)
cout<<"("<<vect[i].first<<","<<vect[i].second<<")\n";
return 0 ;
}
知道其輸出結果是什么了吧:
(10,0)
(9,3)
(8,6)
(7,9)
(6,12)
(5,15)
(4,18)
(3,21)
(2,24)
(1,27)
after sorted by first:
(1,27)
(2,24)
(3,21)
(4,18)
(5,15)
(6,12)
(7,9)
(8,6)
(9,3)
(10,0)
after sorted by second:
(10,0)
(9,3)
(8,6)
(7,9)
(6,12)
(5,15)
(4,18)
(3,21)
(2,24)
(1,27)
1.3 sort 的穩定性
你發現有sort和stable_sort,還有 partition 和stable_partition, 感到奇怪吧。其中的區別是,帶有stable的函數可保證相等元素的原本相對次序在排序后保持不變。或許你會問,既然相等,你還管他相對位置呢,也分不清楚誰是誰了?這里需要弄清楚一個問題,這里的相等,是指你提供的函數表示兩個元素相等,并不一定是一摸一樣的元素。
例如,如果你寫一個比較函數:
bool less_len(const string &str1, const string &str2)
{
return str1.length() < str2.length();
}
此時,"apple" 和 "winter" 就是相等的,如果在"apple" 出現在"winter"前面,用帶stable的函數排序后,他們的次序一定不變,如果你使用的是不帶"stable"的函數排序,那么排序完后,"Winter"有可能在"apple"的前面。
1.4 全排序
全排序即把所給定范圍所有的元素按照大小關系順序排列。用于全排序的函數有
template <class RandomAccessIterator>
void sort(RandomAccessIterator first, RandomAccessIterator last);
template <class RandomAccessIterator, class StrictWeakOrdering>
void sort(RandomAccessIterator first, RandomAccessIterator last,
StrictWeakOrdering comp);
template <class RandomAccessIterator>
void stable_sort(RandomAccessIterator first, RandomAccessIterator last);
template <class RandomAccessIterator, class StrictWeakOrdering>
void stable_sort(RandomAccessIterator first, RandomAccessIterator last,
StrictWeakOrdering comp);
在第1,3種形式中,sort 和 stable_sort都沒有指定比較函數,系統會默認使用operator< 對區間[first,last)內的所有元素進行排序, 因此,如果你使用的類型義軍已經重載了operator<函數,那么你可以省心了。第2, 4種形式,你可以隨意指定比較函數,應用更為靈活一些。來看看實際應用:
班上有10個學生,我想知道他們的成績排名。
#include <iostream>
#include <algorithm>
#include <functional>
#include <vector>
#include <string>
using namespace std;
class student{
public:
student(const string &a, int b):name(a), score(b){}
string name;
int score;
bool operator < (const student &m)const {
return score< m.score;
}
};
int main() {
vector< student> vect;
student st1("Tom", 74);
vect.push_back(st1);
st1.name="Jimy";
st1.score=56;
vect.push_back(st1);
st1.name="Mary";
st1.score=92;
vect.push_back(st1);
st1.name="Jessy";
st1.score=85;
vect.push_back(st1);
st1.name="Jone";
st1.score=56;
vect.push_back(st1);
st1.name="Bush";
st1.score=52;
vect.push_back(st1);
st1.name="Winter";
st1.score=77;
vect.push_back(st1);
st1.name="Andyer";
st1.score=63;
vect.push_back(st1);
st1.name="Lily";
st1.score=76;
vect.push_back(st1);
st1.name="Maryia";
st1.score=89;
vect.push_back(st1);
cout<<"------before sort..."<<endl;
for(int i = 0 ; i < vect.size(); i ++) cout<<vect[i].name<<":\t"<<vect[i].score<<endl;
stable_sort(vect.begin(), vect.end(),less<student>());
cout <<"-----after sort ...."<<endl;
for(int i = 0 ; i < vect.size(); i ++) cout<<vect[i].name<<":\t"<<vect[i].score<<endl;
return 0 ;
}
其輸出是:
------before sort...
Tom: 74
Jimy: 56
Mary: 92
Jessy: 85
Jone: 56
Bush: 52
Winter: 77
Andyer: 63
Lily: 76
Maryia: 89
-----after sort ....
Bush: 52
Jimy: 56
Jone: 56
Andyer: 63
Tom: 74
Lily: 76
Winter: 77
Jessy: 85
Maryia: 89
Mary: 92
sort采用的是成熟的"快速排序算法"(目前大部分STL版本已經不是采用簡單的快速排序,而是結合內插排序算法)。
注1,可以保證很好的平均性能、復雜度為n*log(n),由于單純的快速排序在理論上有最差的情況,性能很低,其算法復雜度為n*n,但目前大部分的STL版本都已經在這方面做了優化,因此你可以放心使用。stable_sort采用的是"歸并排序",分派足夠內存是,其算法復雜度為n*log(n), 否則其復雜度為n*log(n)*log(n),其優點是會保持相等元素之間的相對位置在排序前后保持一致。
1.5 局部排序
局部排序其實是為了減少不必要的操作而提供的排序方式。其函數原型為:
template <class RandomAccessIterator>
void partial_sort(RandomAccessIterator first,
RandomAccessIterator middle,
RandomAccessIterator last);
template <class RandomAccessIterator, class StrictWeakOrdering>
void partial_sort(RandomAccessIterator first,
RandomAccessIterator middle,
RandomAccessIterator last,
StrictWeakOrdering comp);
template <class InputIterator, class RandomAccessIterator>
RandomAccessIterator partial_sort_copy(InputIterator first, InputIterator last,
RandomAccessIterator result_first,
RandomAccessIterator result_last);
template <class InputIterator, class RandomAccessIterator,
class StrictWeakOrdering>
RandomAccessIterator partial_sort_copy(InputIterator first, InputIterator last,
RandomAccessIterator result_first,
RandomAccessIterator result_last, Compare comp);
理解了sort 和stable_sort后,再來理解partial_sort 就比較容易了。先看看其用途: 班上有10個學生,我想知道分數最低的5名是哪些人。如果沒有partial_sort,你就需要用sort把所有人排好序,然后再取前5個。現在你只需要對分數最低5名排序,把上面的程序做如下修改:
stable_sort(vect.begin(), vect.end(),less<student>());
替換為:
partial_sort(vect.begin(), vect.begin()+5, vect.end(),less<student>());
輸出結果為:
------before sort...
Tom: 74
Jimy: 56
Mary: 92
Jessy: 85
Jone: 56
Bush: 52
Winter: 77
Andyer: 63
Lily: 76
Maryia: 89
-----after sort ....
Bush: 52
Jimy: 56
Jone: 56
Andyer: 63
Tom: 74
Mary: 92
Jessy: 85
Winter: 77
Lily: 76
Maryia: 89
這樣的好處知道了嗎?當數據量小的時候可能看不出優勢,如果是100萬學生,我想找分數最少的5個人......
partial_sort采用的堆排序(heapsort),它在任何情況下的復雜度都是n*log(n). 如果你希望用partial_sort來實現全排序,你只要讓middle=last就可以了。
partial_sort_copy其實是copy和partial_sort的組合。被排序(被復制)的數量是[first, last)和[result_first, result_last)中區間較小的那個。如果[result_first, result_last)區間大于[first, last)區間,那么partial_sort相當于copy和sort的組合。
1.6 nth_element 指定元素排序
nth_element一個容易看懂但解釋比較麻煩的排序。用例子說會更方便:
班上有10個學生,我想知道分數排在倒數第4名的學生。
如果要滿足上述需求,可以用sort排好序,然后取第4位(因為是由小到大排), 更聰明的朋友會用partial_sort, 只排前4位,然后得到第4位。其實這是你還是浪費,因為前兩位你根本沒有必要排序,此時,你就需要nth_element:
template <class RandomAccessIterator>
void nth_element(RandomAccessIterator first, RandomAccessIterator nth,
RandomAccessIterator last);
template <class RandomAccessIterator, class StrictWeakOrdering>
void nth_element(RandomAccessIterator first, RandomAccessIterator nth,
RandomAccessIterator last, StrictWeakOrdering comp);
對于上述實例需求,你只需要按下面要求修改1.4中的程序:
stable_sort(vect.begin(), vect.end(),less<student>());
替換為:
nth_element(vect.begin(), vect.begin()+3, vect.end(),less<student>());
運行結果為:
------before sort...
Tom: 74
Jimy: 56
Mary: 92
Jessy: 85
Jone: 56
Bush: 52
Winter: 77
Andyer: 63
Lily: 76
Maryia: 89
-----after sort ....
Jone: 56
Bush: 52
Jimy: 56
Andyer: 63
Jessy: 85
Mary: 92
Winter: 77
Tom: 74
Lily: 76
Maryia: 89
第四個是誰?Andyer,這個倒霉的家伙。為什么是begin()+3而不是+4? 我開始寫這篇文章的時候也沒有在意,后來在
ilovevc 的提醒下,發現了這個問題。begin()是第一個,begin()+1是第二個,... begin()+3當然就是第四個了。
1.7 partition 和stable_partition
好像這兩個函數并不是用來排序的,'分類'算法,會更加貼切一些。partition就是把一個區間中的元素按照某個條件分成兩類。其函數原型為:
template <class ForwardIterator, class Predicate>
ForwardIterator partition(ForwardIterator first,
ForwardIterator last, Predicate pred)
template <class ForwardIterator, class Predicate>
ForwardIterator stable_partition(ForwardIterator first, ForwardIterator last,
Predicate pred);
看看應用吧:班上10個學生,計算所有沒有及格(低于60分)的學生。你只需要按照下面格式替換1.4中的程序:
stable_sort(vect.begin(), vect.end(),less<student>());
替換為:
student exam("pass", 60);
stable_partition(vect.begin(), vect.end(), bind2nd(less<student>(), exam));
其輸出結果為:
------before sort...
Tom: 74
Jimy: 56
Mary: 92
Jessy: 85
Jone: 56
Bush: 52
Winter: 77
Andyer: 63
Lily: 76
Maryia: 89
-----after sort ....
Jimy: 56
Jone: 56
Bush: 52
Tom: 74
Mary: 92
Jessy: 85
Winter: 77
Andyer: 63
Lily: 76
Maryia: 89
看見了嗎,Jimy,Jone, Bush(難怪說美國總統比較笨

)都沒有及格。而且使用的是stable_partition, 元素之間的相對次序是沒有變.
2 Sort 和容器
STL中標準容器主要vector, list, deque, string, set, multiset, map, multimay, 其中set, multiset, map, multimap都是以樹結構的方式存儲其元素詳細內容請參看:
學習STL map, STL set之數據結構基礎. 因此在這些容器中,元素一直是有序的。
這些容器的迭代器類型并不是隨機型迭代器,因此,上述的那些排序函數,對于這些容器是不可用的。上述sort函數對于下列容器是可用的:
如果你自己定義的容器也支持隨機型迭代器,那么使用排序算法是沒有任何問題的。
對于list容器,list自帶一個sort成員函數list::sort(). 它和算法函數中的sort差不多,但是list::sort是基于指針的方式排序,也就是說,所有的數據移動和比較都是此用指針的方式實現,因此排序后的迭代器一直保持有效(vector中sort后的迭代器會失效).
3 選擇合適的排序函數
為什么要選擇合適的排序函數?可能你并不關心效率(這里的效率指的是程序運行時間), 或者說你的數據量很小, 因此你覺得隨便用哪個函數都無關緊要。
其實不然,即使你不關心效率,如果你選擇合適的排序函數,你會讓你的代碼更容易讓人明白,你會讓你的代碼更有擴充性,逐漸養成一個良好的習慣,很重要吧
。
如果你以前有用過C語言中的qsort, 想知道qsort和他們的比較,那我告訴你,qsort和sort是一樣的,因為他們采用的都是快速排序。從效率上看,以下幾種sort算法的是一個排序,效率由高到低(耗時由小變大):
- partion
- stable_partition
- nth_element
- partial_sort
- sort
- stable_sort
記得,以前翻譯過Effective STL的文章,其中對
如何選擇排序函數總結的很好:
- 若需對vector, string, deque, 或 array容器進行全排序,你可選擇sort或stable_sort;
- 若只需對vector, string, deque, 或 array容器中取得top n的元素,部分排序partial_sort是首選.
- 若對于vector, string, deque, 或array容器,你需要找到第n個位置的元素或者你需要得到top n且不關系top n中的內部順序,nth_element是最理想的;
- 若你需要從標準序列容器或者array中把滿足某個條件或者不滿足某個條件的元素分開,你最好使用partition或stable_partition;
- 若使用的list容器,你可以直接使用partition和stable_partition算法,你可以使用list::sort代替sort和stable_sort排序。若你需要得到partial_sort或nth_element的排序效果,你必須間接使用。正如上面介紹的有幾種方式可以選擇。
總之記住一句話:
如果你想節約時間,不要走彎路, 也不要走多余的路!
4 小結
討論技術就像個無底洞,經常容易由一點可以引申另外無數個技術點。因此需要從全局的角度來觀察問題,就像觀察STL中的sort算法一樣。其實在STL還有make_heap, sort_heap等排序算法。本文章沒有提到。本文以實例的方式,解釋了STL中排序算法的特性,并總結了在實際情況下應如何選擇合適的算法。
5 參考文檔
條款31:如何選擇排序函數 The Standard Librarian: Sorting in the Standard Library Effective STL中文版 Standard Template Library Programmer's Guide