當(dāng)前位置:首頁 > 芯聞號 > 充電吧
[導(dǎo)讀]原文轉(zhuǎn)載于:https://www.cnblogs.com/5211314jackrose/p/5816066.html1、異步I/O模式?? ?通常,當(dāng)SQLite寫一個數(shù)據(jù)庫文件時,會等待,直到寫

原文轉(zhuǎn)載于:https://www.cnblogs.com/5211314jackrose/p/5816066.html

1、異步I/O模式
?? ?通常,當(dāng)SQLite寫一個數(shù)據(jù)庫文件時,會等待,直到寫操作完成,然后控制返回到調(diào)用程序。相比于CPU操作,寫文件系統(tǒng)是非常耗時的,這是一個性能瓶頸。異步I/O后端是SQLite的一個擴(kuò)展模塊,允許SQLite使用一個獨(dú)立的后臺線程來執(zhí)行所有的寫請求。雖然這并不會減少整個系統(tǒng)的資源消耗(CPU、磁盤帶寬等),但它允許SQLite在正在寫數(shù)據(jù)庫時立刻返回到調(diào)用者,從用戶角度看,無疑提高了前端的響應(yīng)速度。對異步I/O,寫請求在一個獨(dú)立的后臺線程中被處理,這意味著啟動數(shù)據(jù)庫寫操作的線程不必等待磁盤I/O的發(fā)生。寫操作看起來似乎很快就發(fā)生了,但實(shí)際上速度跟通常是一樣的,只不過在后臺進(jìn)行。
?? ?異步I/O似乎提供了更好的響應(yīng)能力,但這是有代價的。你會失去ACID中的持久性(Durable)屬性。在SQLite的缺省I/O后端中,一旦寫操作完成,你知道更改的數(shù)據(jù)已經(jīng)安全地在磁盤上了。而異步I/O卻不是這樣的情況。如果應(yīng)用程序在數(shù)據(jù)寫操作之后,異步寫線程完成之前發(fā)生崩潰或掉電,則數(shù)據(jù)庫更改可能根本沒有被寫到磁盤,下一次使用數(shù)據(jù)庫時就看不到更改。
?? ?異步I/O失去了持久性,但仍然保持ACID的其他三個屬性:原子性(Atomic)、一致性(Consistent)和隔離性(Isolated)。很多應(yīng)用程序沒有持久性也能很好地工作。
?? ?我們通過創(chuàng)建一個SQLite VFS對象并且用sqlite3_vfs_register()注冊它來使用異步I/O模式。當(dāng)用這個VFS打開數(shù)據(jù)庫文件并進(jìn)行寫操作時(使用vfs的xWrite()方法),數(shù)據(jù)不會立刻寫到磁盤,而是放在由后臺線程維護(hù)的寫隊(duì)列中。當(dāng)用異步VFS打開數(shù)據(jù)庫文件并進(jìn)行讀操作時(使用vfs的xRead()方法),數(shù)據(jù)從磁盤讀出,而寫隊(duì)列從vfs讀進(jìn)程的角度看,其xWrite()已經(jīng)完成了。異步I/O的虛擬文件系統(tǒng)(VFS)通過sqlite3async_initialize()來注冊,通過sqlite3async_shutdown()來關(guān)閉。
?? ?為了積累經(jīng)驗(yàn),異步I/O的實(shí)現(xiàn)有意保持簡單。更多的功能會在將來的版本中添加。例如,在當(dāng)前的實(shí)現(xiàn)中,如果寫操作正在一個穩(wěn)定的流上發(fā)生,而這個流超過了后臺寫線程的I/O能力,則掛起的寫操作隊(duì)列將會無限地增長,可能會耗盡主機(jī)系統(tǒng)的內(nèi)存。復(fù)雜一點(diǎn)的模塊則可以跟蹤掛起的寫操作數(shù)量,在超過一定數(shù)目后停止接收新的寫請求。
?? ?在單個進(jìn)程中、使用異步IO的多個連接可以并發(fā)地訪問單個數(shù)據(jù)庫。從用戶的角度看,如果所有連接都位于單個進(jìn)程中,則正常SQLite和使用異步IO的SQLite,其并發(fā)性并沒有什么不同。如果文件鎖是激活的(缺省是激活的),來自多個進(jìn)程的連接都要讀和寫數(shù)據(jù)庫文件,則并發(fā)性在下面的情況下會減弱:
?? ?(1)當(dāng)使用異步IO的連接啟動一個數(shù)據(jù)庫事務(wù)時,數(shù)據(jù)庫會立刻被鎖住。然而鎖只有在寫隊(duì)列中的所有操作已經(jīng)刷新到磁盤后才能釋放。這意味著有時即使在一個"COMMIT"或"ROLLBACK"執(zhí)行完后,數(shù)據(jù)庫可能仍然處于鎖住狀態(tài)。
?? ?(2)如果應(yīng)用程序使用異步IO連續(xù)地執(zhí)行多個事務(wù),其他數(shù)據(jù)庫用戶可能會因?yàn)閿?shù)據(jù)庫一直被鎖住而不能使用數(shù)據(jù)庫。這是因?yàn)楫?dāng)一個BEGIN執(zhí)行后,數(shù)據(jù)庫鎖會立刻建立起來。但當(dāng)對應(yīng)的COMMIT或ROLLBACK發(fā)生時,鎖不一定釋放了,要到后臺寫隊(duì)列全部刷新到磁盤后才能釋放。如果后臺寫隊(duì)列還沒刷新完,數(shù)據(jù)庫就一直處于鎖住狀態(tài),其他進(jìn)程不能訪問數(shù)據(jù)庫。
?? ?文件鎖可以在運(yùn)行時通過sqlite3async_control()函數(shù)禁用。對NFS這可以提高性能,因?yàn)榭梢员苊鈱Ψ?wù)器的來回異步操作建立文件鎖。但是如果多個連接嘗試訪問同一個數(shù)據(jù)庫,而文件鎖被禁用了,則應(yīng)用程序崩潰和數(shù)據(jù)庫損壞就可能發(fā)生。
?? ?異步IO擴(kuò)展模塊由單個源文件sqlite3async.c,和一個頭文件sqlite3async.h組成,位于源碼樹的ext/async/子目錄下。應(yīng)用程序可以用其中定義的C API來激活和控制這個模塊的功能。為了使用異步IO擴(kuò)展,把sqlite3async.c編譯成使用SQLite的應(yīng)用程序的一部分,然后使用sqlite3async.h中定義的API來初始化和配置這個模塊。這些API在sqlite3async.h的注釋中有詳細(xì)說明,使用這些API通常有以下步驟:
?? ?(1)調(diào)用sqlite3async_initialize()來給SQLite注冊異步IO VFS(虛擬文件系統(tǒng))。
?? ?(2)創(chuàng)建一個后臺線程來執(zhí)行寫操作,并調(diào)用sqlite3async_run()。
?? ?(3)通過異步IO VFS,使用正常的SQLite API來讀寫數(shù)據(jù)庫。
?? ?當(dāng)前的異步IO擴(kuò)展兼容win32系統(tǒng)和支持pthread接口的系統(tǒng),包括Mac OS X, Linux和其他Unix變體。為了移植異步IO擴(kuò)展到其他的平臺,用戶必須在新平臺上實(shí)現(xiàn)互斥鎖和條件變量原語。當(dāng)前并沒有外部可用接口來允許做這樣的控制,但是修改sqlite3async.c中的代碼以包含新平臺的并發(fā)控制原語是相當(dāng)容易的,更多細(xì)節(jié)可搜索sqlite3async.c中的注釋串"PORTING FUNCTIONS"。然后實(shí)現(xiàn)下面這些函數(shù)的新版本:

static void async_mutex_enter(int eMutex);
static void async_mutex_leave(int eMutex);
static void async_cond_wait(int eCond, int eMutex);
static void async_cond_signal(int eCond);
static void async_sched_yield(void);

?? ?上面這些函數(shù)的功能在sqlite3async.c的注釋中有詳細(xì)描述。


2、共享緩存模式
?? ?從3.3.0版開始,SQLite包含一個特別的“共享緩存”模式(缺省情況下禁用),主要用在嵌入式服務(wù)器中。如果共享緩存模式激活,并且一個線程在同一個數(shù)據(jù)庫上建立多個連接,則這些連接共享一個數(shù)據(jù)和模式緩存。這能夠顯著減少系統(tǒng)的內(nèi)存和IO消耗。在3.5.0版中,共享緩存模式被修改以便同一緩存的共享可以跨越整個進(jìn)程而不只是單個線程。在這個修改之前,在線程間傳遞數(shù)據(jù)連接是受限制的。從3.5.0版開始這個限制就消除了。

?? ?從另一個進(jìn)程或線程的角度看,使用共享緩存的兩個或多個數(shù)據(jù)庫連接看起來就像是一個連接。鎖協(xié)議用來在多個共享緩存或數(shù)據(jù)庫用戶之間進(jìn)行仲裁。

圖1 共享緩存模式

??? 圖1描述一個運(yùn)行時配置的例子,有三個數(shù)據(jù)庫連接。連接1是一個正常的SQLite數(shù)據(jù)庫連接,連接2和3共享一個緩存。正常的鎖協(xié)議用來在連接1和共享緩存之間串行化數(shù)據(jù)庫訪問。而連接2和連接3對共享緩存訪問的串行化則有專門的內(nèi)部協(xié)議。見下面的描述。
?? ?有三個級別的共享緩存加鎖模型,事務(wù)級別的加鎖,表級別的加鎖和模式級別的加鎖。

?? ?(1)事務(wù)級別的加鎖
?? ?SQLite連接可能打開兩種類型的事務(wù),讀事務(wù)和寫事務(wù)。這不是顯式完成的,一個事務(wù)隱式地含有一個讀事務(wù),直到它首次寫一個數(shù)據(jù)庫文件,這時成為一個寫事務(wù)。在任何時候共享緩存上最多只能有一個連接打開一個寫事務(wù),這個寫事務(wù)可以和任何數(shù)量的讀事務(wù)共存。這與非共享緩存模式不同,非共享緩存模式下有讀操作時不允許有寫操作。

?? ?(2)表級別的加鎖
?? ?當(dāng)兩個或更多的連接使用一個共享緩存,用鎖來串行化每個表格的并發(fā)訪問。表支持兩種類型的鎖,讀鎖和寫鎖。鎖被授予連接,任何時候每個數(shù)據(jù)庫連接上的每個表格可以有讀鎖、寫鎖或沒有鎖。一個表格上可以任何數(shù)量的讀鎖,但只能有一個寫鎖。讀數(shù)據(jù)庫表格時必須首先獲得一個讀鎖。寫表格時必須獲得一個寫鎖。如果不能獲取需要的鎖,查詢失敗并返回SQLITE_LOCKED給調(diào)用者。表級別的鎖在獲取之后,要到當(dāng)前事務(wù)(讀或?qū)懀┙Y(jié)束時才釋放。
?? ?如果使用read_uncommitted pragma指令把事務(wù)隔離模式從串行(serialized,缺省模式,即查詢數(shù)據(jù)時會加上共享瑣,阻塞其他事務(wù)修改真實(shí)數(shù)據(jù))改成允許臟讀(read-uncommitted,即SELECT會讀取其他事務(wù)修改而還沒有提交的數(shù)據(jù)),則上面描述的行為會有稍許的變化。事務(wù)隔離模式還有另外兩種,無法重復(fù)讀read-comitted是同一個事務(wù)中兩次執(zhí)行同樣的查詢語句,若在第一次與第二次查詢之間時間段,其他事務(wù)又剛好修改了其查詢的數(shù)據(jù)且提交了,則兩次讀到的數(shù)據(jù)不一致。可以重復(fù)讀read-repeatable是指同一個事務(wù)中兩次執(zhí)行同樣的查詢語句,得到的數(shù)據(jù)始終都是一致的。

/* Set the value of the read-uncommitted flag: 
  ** 
  **   True  -> Set the connection to read-uncommitted mode. 
  **   False -> Set the connection to serialized (the default) mode. 
  */  
  PRAGMA read_uncommitted = ;  
  
  /* Retrieve the current value of the read-uncommitted flag */  
  PRAGMA read_uncommitted; 

? ? 允許臟讀模式的數(shù)據(jù)庫連接在讀數(shù)據(jù)庫表時不會獲取讀鎖,如果這時另外一個數(shù)據(jù)庫連接修改了正在被讀的表數(shù)據(jù),則可能導(dǎo)致查詢結(jié)果不一致,因?yàn)樵试S臟讀模式的讀事務(wù)不會被打斷。允許臟讀模式不會影響寫事務(wù),它必須獲取寫鎖,因此數(shù)據(jù)庫寫操作可以被阻塞。允許臟讀模式也不會影響sqlite_master級別的鎖。

?? ?(3)模式(sqlite_master)級別的加鎖
?? ?sqlite_master表支持與其他數(shù)據(jù)庫表相同的共享緩存讀鎖和寫鎖。還會使用下面的特殊規(guī)則:
?? ?* 在訪問任何數(shù)據(jù)庫表格或者獲取任何其他的讀鎖和寫鎖之前,連接必須先獲取一個sqlite_master表上的讀鎖。
?? ?* 在執(zhí)行修改數(shù)據(jù)庫模式的語句(例如CREATE TABLE或DROP TABLE)之前,連接必須先獲取一個sqlite_master表上的寫鎖。
?? ?* 如果任何其他的連接持有關(guān)聯(lián)數(shù)據(jù)庫(包括缺省的主數(shù)據(jù)庫)的sqlite_master表上的寫鎖,則連接不可以編譯一個SQL語句。
?? ?在SQLite 3.3.0到3.4.2之間,數(shù)據(jù)庫連接只能被調(diào)用sqlite3_open()創(chuàng)建它的線程使用,一個連接只能與同一線程中的其他連接共享緩存。從SQLite 3.5.0開始,這個限制消除了。在老版本的SQLite上,共享緩存模式不能使用在虛擬表上,從SQLite 3.6.17開始,這個限制消除了。
?? ?共享緩存模式在每個進(jìn)程級別上激活。C接口int sqlite3_enable_shared_cache(int)用來全局地激活或禁用共享緩存模式。每次調(diào)用sqlite3_enable_shared_cache()影響后續(xù)的使用sqlite3_open(), sqlite3_open16()或sqlite3_open_v2()創(chuàng)建的數(shù)據(jù)庫連接,已經(jīng)存在的數(shù)據(jù)庫連接則不受影響。每次sqlite3_enable_shared_cache()的調(diào)用覆蓋進(jìn)程上的前面各次調(diào)用。
?? ?使用sqlite3_open_v2()創(chuàng)建的單個數(shù)據(jù)庫連接,通過在第三個參數(shù)上使用SQLITE_OPEN_SHAREDCACHE或SQLITE_OPEN_PRIVATECACHE標(biāo)志,可能選擇參與或不參與共享緩存模式。在該數(shù)據(jù)庫連接上這些標(biāo)志會覆蓋全局的sqlite3_enable_shared_cache()設(shè)置。如果同時使用這兩個標(biāo)志,則行為是未定義的。
?? ?當(dāng)使用URI文件名時,"cache"查詢參數(shù)可以用來指定連接是否使用共享緩存模式。"cache=shared"激活共享緩存,"cache=private"禁用共享緩存。例如:
?? ?ATTACH 'file:aux.db?cache=shared' AS aux;
?? ?從SQLite 3.7.13開始,倘若數(shù)據(jù)庫使用URI文件名創(chuàng)建,共享緩存模式可以在內(nèi)存數(shù)據(jù)庫上使用。為了向后兼容,使用未修飾的":memory:"名稱打開內(nèi)存數(shù)據(jù)庫時缺省是禁用共享緩存的。而在SQLite 3.7.13之前,無論使用的內(nèi)存數(shù)據(jù)庫名、當(dāng)前系統(tǒng)的共享緩存設(shè)置、以及查詢參數(shù)或標(biāo)志是什么,內(nèi)存數(shù)據(jù)庫上共享緩存總是被禁用的。
?? ?在內(nèi)存數(shù)據(jù)庫上激活共享緩存,會允許同一進(jìn)程上的兩個或更多數(shù)據(jù)庫連接訪問同一段內(nèi)存。當(dāng)最后一個連接關(guān)閉時,內(nèi)存數(shù)據(jù)庫會自動刪除,這段內(nèi)存也會被重置。
?? ?

3、解鎖通知
?? ?當(dāng)多個連接在共享緩存模式下訪問同一個數(shù)據(jù)庫時,單個表上的讀鎖和寫鎖(即共享鎖和排他鎖)用來確保并發(fā)執(zhí)行的事務(wù)是隔離的。如果連接不能獲取到需要的鎖,sqlite3_step()調(diào)用返回SQLITE_LOCKED。如果不能獲取到每個關(guān)聯(lián)數(shù)據(jù)庫的sqlite_master表上的讀鎖(雖然這種情況并不常見),sqlite3_prepare()或sqlite3_prepare_v2()調(diào)用也會返回SQLITE_LOCKED。
?? ?通過使用SQLite的sqlite3_unlock_notify()接口,我們可以讓sqlite3_step()或sqlite3_prepare_v2()調(diào)用阻塞直到獲得需要的鎖,而不是立刻返回SQLITE_LOCKED。下面的例子展示解鎖通知的使用。

/* 本例子使用pthreads API */  
#include   
  
/* 
** 當(dāng)注冊一個解鎖通知時,傳遞本結(jié)構(gòu)實(shí)例的指針,以作為用戶上下文中的實(shí)例 
*/  
typedef struct UnlockNotification UnlockNotification;  
struct UnlockNotification {  
  int fired;                         /* 在解鎖事件發(fā)生后為True */  
  pthread_cond_t cond;               /* 要等待的條件變量 */  
  pthread_mutex_t mutex;             /* 保護(hù)本結(jié)構(gòu)的互斥量 */  
};  
  
/* 
** 解鎖通知回調(diào)函數(shù) 
*/  
static void unlock_notify_cb(void **apArg, int nArg){  
  int i;  
  for(i=0; imutex);  /* 對臨界區(qū)加鎖 */  
    p->fired = 1;  /* 觸發(fā)解鎖事件,本變量只能互斥訪問 */  
    pthread_cond_signal(&p->cond);  
    pthread_mutex_unlock(&p->mutex);  
  }  
}  
  
/* 
** 本函數(shù)假設(shè)SQLite API調(diào)用(sqlite3_prepare_v2()或sqlite3_step())返回SQLITE_LOCKED。 
** 參數(shù)為關(guān)聯(lián)的數(shù)據(jù)庫連接。 
** 本函數(shù)調(diào)用sqlite3_unlock_notify()注冊一個解鎖通知回調(diào)函數(shù),然后阻塞直到 
** 回調(diào)函數(shù)執(zhí)行完并返回SQLITE_OK。調(diào)用者應(yīng)該重試失敗的操作。 
** 或者,如果sqlite3_unlock_notify()指示阻塞將會導(dǎo)致系統(tǒng)死鎖,則本函數(shù)立刻 
** 返回SQLITE_LOCKED。調(diào)用者不應(yīng)該重試失敗的操作,而是回滾當(dāng)前事務(wù) 
*/  
static int wait_for_unlock_notify(sqlite3 *db){  
  int rc;  
  UnlockNotification un;  
  
  /* 初始化UnlockNotification結(jié)構(gòu) */  
  un.fired = 0;  
  pthread_mutex_init(&un.mutex, 0);  
  pthread_cond_init(&un.cond, 0);  
  
  /* 注冊一個解鎖通知回調(diào)函數(shù) */  
  rc = sqlite3_unlock_notify(db, unlock_notify_cb, (void *)&un);  
  assert( rc==SQLITE_LOCKED || rc==SQLITE_OK );  
  
  /* sqlite3_unlock_notify()調(diào)用總是返回SQLITE_LOCKED或SQLITE_OK。 
  ** 如果返回SQLITE_LOCKED,則系統(tǒng)死鎖。本函數(shù)需要返回SQLITE_LOCKED給調(diào)用者以 
  ** 便當(dāng)前事務(wù)能夠回滾。否則阻塞直到解鎖通知回調(diào)函數(shù)執(zhí)行,然后返回SQLITE_OK 
  */  
  if( rc==SQLITE_OK ){  
    pthread_mutex_lock(&un.mutex);  
    if( !un.fired ){ /* 如果解鎖事件沒有發(fā)生,則阻塞 */  
      pthread_cond_wait(&un.cond, &un.mutex);  
    }  
    pthread_mutex_unlock(&un.mutex);  
  }  
  
  /* 銷毀互斥量和條件變量 */  
  pthread_cond_destroy(&un.cond);  
  pthread_mutex_destroy(&un.mutex);  
  
  return rc;  
}  
  
/* 
** 本函數(shù)是SQLite函數(shù)sqlite3_step()的包裝,它的工作方式與sqlite3_step()相同。 
** 但如果沒有獲得共享緩存鎖,則本函數(shù)阻塞以等待鎖可用。 
** 如果本函數(shù)返回SQLITE_LOCKED,調(diào)用者應(yīng)該回滾當(dāng)前事務(wù),之后再嘗試。否則系統(tǒng)可能死鎖了 
*/  
int sqlite3_blocking_step(sqlite3_stmt *pStmt){  
  int rc;  
  while( SQLITE_LOCKED==(rc = sqlite3_step(pStmt)) ){  
    rc = wait_for_unlock_notify(sqlite3_db_handle(pStmt));  
    if( rc!=SQLITE_OK ) break;  
    sqlite3_reset(pStmt);  
  }  
  return rc;  
}  
  
/* 
** 本函數(shù)是SQLite函數(shù)sqlite3_prepare_v2()的包裝,它的工作方式與sqlite3_prepare_v2()相同。 
** 但如果沒有獲得共享緩存鎖,則本函數(shù)阻塞以等待鎖可用。 
** 如果本函數(shù)返回SQLITE_LOCKED,調(diào)用者應(yīng)該回滾當(dāng)前事務(wù),之后再嘗試。否則系統(tǒng)可能死鎖了 
*/  
int sqlite3_blocking_prepare_v2(  
  sqlite3 *db,              /* 數(shù)據(jù)庫句柄 */  
  const char *zSql,         /* UTF-8編碼的SQL語句 */  
  int nSql,                 /* zSql的字節(jié)數(shù) */  
  sqlite3_stmt **ppStmt,    /* OUT: 指向預(yù)處理語句的指針 */  
  const char **pz           /* OUT: 解析過的字符串尾部位置 */  
){  
  int rc;  
  while( SQLITE_LOCKED==(rc = sqlite3_prepare_v2(db, zSql, nSql, ppStmt, pz)) ){  
    rc = wait_for_unlock_notify(db);  
    if( rc!=SQLITE_OK ) break;  
  }  
  return rc;  
}

? ? 如果例子中的sqlite3_blocking_step()或sqlite3_blocking_prepare_v2()函數(shù)返回SQLITE_LOCKED,則表明阻塞將導(dǎo)致系統(tǒng)死鎖。
?? ?只有在編譯時定義預(yù)處理宏SQLITE_ENABLE_UNLOCK_NOTIFY,才能使用sqlite3_unlock_notify()接口。該接口被設(shè)計(jì)成用在這樣的系統(tǒng)中:每個數(shù)據(jù)庫連接分配單獨(dú)的線程。如果在一個線程中運(yùn)行多個數(shù)據(jù)庫連接,則不能使用該接口。sqlite3_unlock_notify()接口一次只在一個線程上工作,因此上面的鎖控制邏輯只能工作于一個線程的單個數(shù)據(jù)庫連接上。
?? ?上面的例子中,在sqlite3_step()或sqlite3_prepare_v2()返回SQLITE_LOCKED后,sqlite3_unlock_notify()被調(diào)用以注冊一個解鎖通知回調(diào)函數(shù)。在數(shù)據(jù)庫連接持有表級別的鎖后,解鎖通知函數(shù)被執(zhí)行以防止sqlite3_step()或sqlite3_prepare_v2()隨后完成事務(wù)并釋放所有鎖。例如,如果sqlite3_step()嘗試讀表格X,而其他某個連接Y正持有表格X的寫鎖,sqlite3_step()將返回SQLITE_LOCKED。如果隨后調(diào)用sqlite3_unlock_notify(),解鎖通知函數(shù)將在連接Y的事務(wù)結(jié)束后被調(diào)用。解鎖通知函數(shù)正在等待的連接(這里的Y),被稱為“阻塞式連接”。
?? ?如果sqlite3_step()嘗試寫一個數(shù)據(jù)庫,但返回SQLITE_LOCKED,則可能有多個進(jìn)程持有當(dāng)前數(shù)據(jù)庫表格的讀鎖。這時SQLite隨意地選擇其中的一個連接,當(dāng)這個連接的事務(wù)完成時執(zhí)行解鎖通知函數(shù)。解鎖通知函數(shù)從sqlite3_step()(或sqlite3_close())里執(zhí)行,它關(guān)聯(lián)有一個阻塞式進(jìn)程。解鎖通知函數(shù)里面可以調(diào)用任何的sqlite3_XXX()函數(shù),可以向其他等待線程發(fā)信號,或者安排一些在以后要發(fā)生的行為。

?? ?sqlite3_blocking_step()函數(shù)使用的算法描述如下:
?? ?(1)在指定的SQL語句對象上調(diào)用sqlite3_step(),如果返回除SQLITE_LOCKED之外的值,則直接返回這個值給調(diào)用者。如果返回SQLITE_LOCKED則繼續(xù)。
?? ?(2)調(diào)用sqlite3_unlock_notify()注冊一個解鎖通知回調(diào)函數(shù)。如果sqlite3_unlock_notify()返回SQLITE_LOCKED,說明系統(tǒng)死鎖,返回這個值給調(diào)用者以便回滾。否則繼續(xù)。
?? ?(3)阻塞,直到解鎖通知函數(shù)被另外一個線程執(zhí)行。
?? ?(4)在SQL語句對象上調(diào)用sqlite3_reset()。因?yàn)镾QLITE_LOCKED錯誤可能只發(fā)生在第一次調(diào)用sqlite3_step()時(不可能有sqlite3_step()先返回SQLITE_ROW而下一次卻返回SQLITE_LOCKED的情況)。這時SQL語句對象會被重置,從而不會影響查詢結(jié)果。如果不調(diào)用sqlite3_reset(),下一次調(diào)用sqlite3_step()將返回SQLITE_MISUSE。
?? ?(5)轉(zhuǎn)向步驟(1)。
?? ?sqlite3_blocking_prepare_v2()使用的算法也類似,只不過第4步(重置SQL語句對象)忽略。

?? ?對于“寫?zhàn)囸I”現(xiàn)象,SQLite能幫助應(yīng)用程序避免出現(xiàn)寫?zhàn)囸I的情況。當(dāng)在一個表上獲取寫鎖的任何嘗試失敗后(因?yàn)橛羞B接一直持有讀鎖),共享緩存上啟動新事務(wù)的所有嘗試都會失敗,直到下面有一種情況變成true為止:
?? ?* 當(dāng)前寫事務(wù)完成,或者
?? ?* 共享緩存上打開的讀事務(wù)數(shù)量減為0。
?? ?啟動新的讀事務(wù)失敗會返回SQLITE_LOCKED給調(diào)用者。如果調(diào)用者然后調(diào)用sqlite3_unlock_notify()注冊一個解鎖通知函數(shù),阻塞式連接當(dāng)前在共享緩存上會有一個寫事務(wù)。這就避免了寫?zhàn)囸I,因?yàn)闆]有新的讀鎖可以打開了。當(dāng)所有存在的讀鎖完成時,寫操作最終能有機(jī)會獲得需要的寫鎖。
?? ?在wait_for_unlock_notify()調(diào)用sqlite3_unlock_notify()時,有可能阻塞式線程已經(jīng)完成它的事務(wù),這樣在sqlite3_unlock_notify()返回前解鎖通知函數(shù)會立刻被調(diào)用。解鎖通知函數(shù)也有可能被另一個線程調(diào)用,正好發(fā)生在sqlite3_unlock_notify()調(diào)用之后,而在這個線程開始等待異步信號之前。這樣的競爭條件怎么處理,取決于應(yīng)用程序使用的線程和同步原語。本例子中使用pthread,這是現(xiàn)代Unix風(fēng)格的系統(tǒng)(包括Linux)提供的接口。
?? ?pthread提供pthread_cond_wait()函數(shù),它允許調(diào)用者同時釋放一個互斥量并開始等待一個異步信號。使用這個函數(shù)、一個"fired"標(biāo)志和一個互斥量,競爭狀態(tài)可以消除,如下:
?? ?當(dāng)解鎖通知函數(shù)被調(diào)用時,這可能發(fā)生在調(diào)用sqlite3_unlock_notify()的線程開始等待一個異步信號之前,它做下面的工作:
?? ?(1)獲取互斥量。
?? ?(2)設(shè)置"fired"標(biāo)志為true。
?? ?(3)向等待線程發(fā)信號。
?? ?(4)釋放互斥量。
?? ?當(dāng)wait_for_unlock_notify()線程開始等待解鎖通知函數(shù)到達(dá)時,它:
?? ?(1)獲取互斥量。
?? ?(2)檢查"fired"標(biāo)志是否設(shè)置。如果已設(shè)置,解鎖通知函數(shù)已經(jīng)被調(diào)用,直接釋放互斥量,然后繼續(xù)。
?? ?(3)如果沒設(shè)置,原子性地釋放互斥量,并開始等待異步信號。當(dāng)信號到達(dá)時,繼續(xù)。
?? ?通過這種方式,當(dāng)wait_for_unlock_notify()開始阻塞時,解鎖通知函數(shù)不管是已經(jīng)被調(diào)用,還是正在被調(diào)用,都沒有問題。

?? ?本文例子中的代碼至少在以下兩個方面可以改進(jìn):
?? ?* 能管理線程優(yōu)先級。
?? ?* 能處理SQLITE_LOCKED的特殊情形,這可能發(fā)生在刪除一個表或索引時。
?? ?雖然sqlite3_unlock_notify()只允許調(diào)用者指定單個的用戶上下文指針,但一個解鎖通知回調(diào)是傳給這種上下文指針數(shù)組的。這是因?yàn)楫?dāng)一個阻塞式線程完成它的事務(wù)時,如果有多個解鎖通知被注冊用于調(diào)用同一個C函數(shù),則上下文指針就要排列成一個數(shù)組。如果每個線程分配一個優(yōu)先級,則高優(yōu)先級的線程就會比低優(yōu)先級的線程先得到信號通知,而不是以任意的順序來通知線程。
?? ?如果執(zhí)行一個"DROP TABLE"或"DROP INDEX"命令,而當(dāng)前數(shù)據(jù)庫連接上有一個或多個正在執(zhí)行的SELECT語句,則會返回SQLITE_LOCKED。如果調(diào)用了sqlite3_unlock_notify(),指定的回調(diào)函數(shù)立刻會被調(diào)用。重新嘗試"DROP TABLE"或"DROP INDEX"將返回另外一個SQLITE_LOCKED錯誤。在上面的sqlite3_blocking_step()實(shí)現(xiàn)中,這會導(dǎo)致死循環(huán)。
?? ?調(diào)用者可以使用擴(kuò)展錯誤碼來區(qū)別這種特殊的"DROP TABLE|INDEX"情形和其他情形。當(dāng)它正常調(diào)用sqlite3_unlock_notify()時,擴(kuò)展錯誤碼是SQLITE_LOCKED_SHAREDCACHE。在"DROP TABLE|INDEX"情形中,是普通的SQLITE_LOCKED。另外一種解決方法是限制重試單個查詢的次數(shù)(如100次)。雖然這會導(dǎo)致效率低一點(diǎn),但我們這里討論的情況并不是經(jīng)常發(fā)生的。


本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫?dú)角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

加利福尼亞州圣克拉拉縣2024年8月30日 /美通社/ -- 數(shù)字化轉(zhuǎn)型技術(shù)解決方案公司Trianz今天宣布,該公司與Amazon Web Services (AWS)簽訂了...

關(guān)鍵字: AWS AN BSP 數(shù)字化

倫敦2024年8月29日 /美通社/ -- 英國汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動 BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運(yùn)行,同時企業(yè)卻面臨越來越多業(yè)務(wù)中斷的風(fēng)險,如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報(bào)道,騰訊和網(wǎng)易近期正在縮減他們對日本游戲市場的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機(jī) 衛(wèi)星通信

要點(diǎn): 有效應(yīng)對環(huán)境變化,經(jīng)營業(yè)績穩(wěn)中有升 落實(shí)提質(zhì)增效舉措,毛利潤率延續(xù)升勢 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競爭力 堅(jiān)持高質(zhì)量發(fā)展策略,塑強(qiáng)核心競爭優(yōu)勢...

關(guān)鍵字: 通信 BSP 電信運(yùn)營商 數(shù)字經(jīng)濟(jì)

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺與中國電影電視技術(shù)學(xué)會聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會上宣布正式成立。 活動現(xiàn)場 NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會上,軟通動力信息技術(shù)(集團(tuán))股份有限公司(以下簡稱"軟通動力")與長三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉