GStreamer API被設計為線程安全的。這意味着可以同時從多個線程調用API函數。GStreamer在内部使用線程來執行資料傳遞,并且各種異步服務(例如時鐘)也可以使用線程。
該設計決策對API和本文檔說明的對象的使用有影響。
MT安全技術
幾種設計模式用于保證GStreamer中的對象一緻性。這是在各種GStreamer子系統中使用的方法的概述。
引用計數:
所有共享對象都有與其關聯的引用計數。每次獲得對對象的引用都應增加引用計數,而每次丢失對象的引用應減少引用計數。
引用計數用于確定當在另一個線程銷毀該對象時,仍持有對該對象引用的那些線程在通路該對象時不會從無效記憶體中讀取資料。
引用計數還用于確定可變資料結構隻有在調用代碼擁有該對象時才可以被修改。
要求當兩個線程在同一個對象上具有句柄時,引用計數必須大于一個。這意味着當一個線程将對象傳遞給另一線程時,它必須增加引用計數。此要求確定了當一個線程突然釋放對象進而不會導緻另一個線程試圖通路指向無效記憶體的指針而崩潰。
共享的資料結構和可寫性:
所有對象都有與其關聯的引用計數。對于每次擷取對象的引用都應增加引用計數,而每次失去對對象的引用應減少引用計數。
每個引用對象的線程都可以安全地從對象中讀取資料。但對對象所做的修改之前應該調用_get_writable() 。此函數将檢查對象的引用計數,如果該對象被多個執行個體引用,則将複制該對象,然後根據定義僅從調用線程引用該對象。然後,我們可以修改此新副本,而其他引用者看不到該副本。
此技術用于資訊類對象,這些對象一旦建立,就永遠不會更改其值。這些對象的壽命通常很短,這些對象通常很簡單并且複制/建立開銷較低。
此方法的優點是不需要讀取/寫入鎖。所有線程可以同時讀取,但寫入會在新副本上本地進行。在大多數情況下_get_writable(),可以避免使用真實副本,因為調用的方法是唯一擁有對象引用的方法,這使讀取/寫入非常快。
缺點是有時會有1次不必要的複制。這種情況發生在當N個線程同時調用時_get_writable()時,所有人都看到該對象上有N個引用。在這種情況下,會導緻複制次數太多。在任何實際情況下這都不是問題,因為複制操作很快。
可變子結構:
必須使用特殊技術來確定複合共享對象的一緻性。如上所述,如果要修改共享對象,則它們的引用計數必須為1。此假設隐含了共享對象的所有部分僅屬于該對象。例如,GstStructure一個GstCaps對象不應該屬于任何其他GstCaps對象。這種情況表明存在父子關系:隻有在沒有父對象的情況下,才能将結構添加到父對象。
此外,當多個代碼段在父對象上具有引用時,不得修改這些子結構。例如,如果使用者建立了GstStructure,将其添加到中GstCaps,GstCaps然後其他代碼段引用了,則GstStructure應當變為不可變的,是以對該資料結構的更改不會影響代碼的其他部分。這意味着僅當父級的引用計數為1以及子級結構沒有父級時,子級才可變。
解決此問題的一般方法是在子結構中包括一個指向父級原子引用計數的字段。設定為NULL時,表示子級沒有父級。否則,修改子級結構的過程必須檢查父級的引用計數是否為1,否則必須引起錯誤信号。
注意,這是一個内部實作細節。確定調用_get_writable()對象的應用程式或插件代碼接收到引用計數為1的對象,該對象必須是可寫的。唯一的技巧是,僅當調用代碼在父對象上具有引用時,指向對象的子結構的指針才有效,因為父是該子對象的所有者。
對象鎖定:
對于包含狀态資訊且通常具有較長生存期的對象,對象鎖定用于更新對象中包含的資訊。
所有讀取器和寫入器在通路該對象之前均需要獲得該鎖。一次隻允許一個線程通路受保護的結構。
對象鎖定用于所有從對象GstObject的擴充,如 GstElement,GstPad。
可以使用遞歸鎖或正常互斥鎖來完成對象鎖定。GStreamer中的對象鎖是通過互斥對象實作的,當從同一個線程遞歸鎖定時,互斥對象會導緻死鎖。這樣做是因為正常互斥鎖開銷更低。
原子操作
原子操作是一種即使在多個線程中執行也是一個一緻性的操作。但是,他們沒有使用互斥鎖的傳統方法來保護關鍵部分,而是依靠CPU功能和指令。
優點主要與速度相關,因為不涉及重量級的鎖。在并發通路的情況下,這些指令中的大多數指令也不會引起上下文切換,而是使用重試機制或自旋鎖。
缺點是,當兩個處理器執行并發通路時,這些指令中的每一個通常會導緻多CPU計算機上的緩存重新整理。
原子操作通常用于重新計數和在memchunk中配置設定小型固定大小的對象。它們還可以用于實作無鎖清單或堆棧。
比較并交換
作為原子操作的一部分,比較和交換(CAS)可用于通路或更新對象中的單個屬性或指針,而無需進行鎖定。
GStreamer目前未使用此技術,但将來可能會在性能至關重要的地方添加此技術。
對象
鎖定涉及:
- 原子操作引用計數
- 對象鎖定
所有對象都應具有與之關聯的鎖。當多個線程在對象上調用API函數時,此鎖用于保持内部一緻性。
對于擴充自GStreamer基礎類的對象,可以使用宏GST_OBJECT_LOCK()和來獲得此鎖定GST_OBJECT_UNLOCK()。對于不從基GstObject類擴充的其他對象,這些宏可以不同。
引用計數
所有新建立的對象都設定了FLOATING标志。這意味着除了持有該對象引用的人之外,該對象還沒有被任何人擁有或管理。。此狀态下的對象的引用計數為1。
一個對象擁有許多方法可以取得另一個對象的所有權,這意味着在以對象B作為參數調用對象A的方法之後,對象B成為對象A的唯一屬性。這意味着在方法調用之後,除非你保留對該對象的額外引用,否則将不再允許通路該對象。這種方法的一個例子是_bin_add()方法。在Bin中調用此函數後,作為參數傳遞的元素将歸Bin擁有,并且再将其添加到Bin中之前如果沒有調用_ref()你将不被允許再對其進行通路。原因是在_bin_add()調用處理(dispose)bin之後也會銷毀該元素。
擷取對象的所有權是通過“下沉”對象的過程進行的。如果設定了FLOATING标志,則對對象的_sink()方法将減少對象的引用計數。然後,在對象上先執行_ref(),再進行_sink()調用,以擷取對象所有權。
如果我們初始化一個元素并将其控制權限轉移到父類中,那麼浮動/接收過程非常有用。浮動ref使對象保持活動狀态,直到它成為父對象為止;一旦成為父對象,您就可以忽略它。
同樣可以查閱-對象關系類型
父子關系
可以使用該_object_set_parent() 方法建立父子關系。此方法引用和接收對象,并将其父屬性指派為管理父對象。
由于在此過程中父節點的再計數沒有增加,是以子節點與父節點之間存在弱連結。。這意味着,如果父對象被釋放,它必須在釋放自己之前取消自己作為對象的父對象的設定,否則子對象持有一個指向無效記憶體的父指針。
下沉其他對象的對象的職責總結為:
- 取得對象的所有權
-
- 調用_object_set_parent()将自身設定為對象父對象,此調用将_ref()和_sink()對象。
- 在資料結構(例如清單或數組)中保持對對象的引用。
- 處理(dispose)對象
-
- 調用_object_unparent()以重置父屬性并取消引用該對象。
- 從清單中删除該對象。
同樣可以查閱-對象關系類型
屬性
大多數對象還公開對象中具有公共屬性的狀态資訊。可能存在兩種類型的屬性:持有或不持有對象鎖均可通路。所有屬性都應該隻使用其相應的宏進行通路。公共對象屬性用/ <public> /标記在.h檔案中。需要持有鎖的公共屬性用标記 ,在<lock_type>可以是LOCK或STATE_LOCK或任何其他鎖來标記要持有的鎖的類型。
範例:
在GstPad有一個公共屬性direction。可以在标記為public 并需要保持LOCK的部分中找到它。還有一個宏可以通路該屬性。
struct _GstRealPad {
...
/*< public >*/ /* with LOCK */
...
GstPadDirection direction;
...
};
#define GST_RPAD_DIRECTION(pad) (GST_REAL_PAD_CAST(pad)->direction)
是以,以下代碼示例允許通路該屬性:
GST_OBJECT_LOCK (pad);
direction = GST_RPAD_DIRECTION (pad);
GST_OBJECT_UNLOCK (pad);
屬性的生命周期
釋放關聯的鎖定後,所有需要鎖定的屬性都可以更改。這意味着隻要您持有鎖,關于鎖屬性的對象狀态就與擷取的資訊一緻。釋放鎖後,從屬性擷取的任何值都可能不再有效,并且最好将其描述為持有鎖的狀态的快照。
這意味着在釋放鎖定之前,應複制或重新引用需要超出臨界區範圍通路的所有屬性。
大多數對象都提供了_get_<property>()一種擷取屬性值的副本或重新整理執行個體的方法。調用方不應擔心任何鎖,而應在使用後取消引用/釋放該對象。
範例:
下面的示例正确擷取元素的對等襯墊。它需要增加對端pad的重計數,因為一旦鎖被釋放,對端就可能被取消和釋放,使得在臨界區獲得的指針指向無效記憶體。
GST_OBJECT_LOCK (pad);
peer = GST_RPAD_PEER (pad);
if (peer)
gst_object_ref (GST_OBJECT (peer));
GST_OBJECT_UNLOCK (pad);
... use peer ...
if (peer)
gst_object_unref (GST_OBJECT (peer));
請注意,釋放鎖定後,對等端實際上可能不再是該襯墊中的對等端。如果需要确定,則需要擴充臨界段以包括在對等端上的操作。
以下代碼與上面的代碼等效,但是使用這些函數來通路對象屬性。
peer = gst_pad_get_peer (pad);
if (peer) {
... use peer ...
gst_object_unref (GST_OBJECT (peer));
}
範例:
通路對象的名稱将複制該名稱。函數的調用者應g_free()在使用後命名。
GST_OBJECT_LOCK (object)
name = g_strdup (GST_OBJECT_NAME (object));
GST_OBJECT_UNLOCK (object)
... use name ...
g_free (name);
或者:
name = gst_object_get_name (object);
... use name ...
g_free (name);
通路器方法
對于應用程式來講,鼓勵使用對象的公共方法。可以使用這些方法執行大部分有用的操作,是以很少需要手動通路公共字段。
所有傳回對象的通路器方法都應增加傳回對象的引用計數。調用方在對象的使用完成後應該調用_unref()。每種方法都應在文檔中說明這種引用計數的政策。
存取清單
如果對象屬性是連結清單,則需要并發連結清單疊代才能擷取清單的内容。GStreamer使用cookie機制來标記連結清單的最後更新。連結清單和cookie受同一鎖保護。連結清單的每次更新都需要執行以下操作:
- 擷取鎖
- 更新連結清單
- 更新cookie
- 釋放鎖
更新cookie通常是通過将其值增加1來完成的。由于cookie使用guint32,是以出于實際原因,它的環繞不是問題。
可以通過用鎖的上鎖/解鎖包圍連結清單疊代器來安全地完成連結清單的疊代。
在某些情況下,疊代清單時長時間保持鎖定不是一個好主意。例如,GStreamer中bin的狀态更改代碼必須周遊每個元素并對每個元素執行阻塞調用,這可能會導緻bin被無限鎖定。在這種情況下,可以使用cookie來疊代清單。
範例:
在疊代過程中對清單進行并發更新的情況下,以下算法對清單進行疊代并撤消更新。這樣的想法是,每當我們重新擷取鎖時,我們都會檢查cookie的更新,以确定我們是否仍在疊代正确的清單。
GST_OBJECT_LOCK (lock);
/* grab list and cookie */
cookie = object->list_cookie;
list = object->list;
while (list) {
GstObject *item = GST_OBJECT (list->data);
/* need to ref the item before releasing the lock */
gst_object_ref (item);
GST_OBJECT_UNLOCK (lock);
... use/change item here...
/* release item here */
gst_object_unref (item);
GST_OBJECT_LOCK (lock);
if (cookie != object->list_cookie) {
/* handle rollback caused by concurrent modification
* of the list here */
...rollback changes to items...
/* grab new cookie and list */
cookie = object->list_cookie;
list = object->list;
}
else {
list = g_list_next (list);
}
}
GST_OBJECT_UNLOCK (lock);
GstIterator
GstIterator提供了一種檢索并發清單中元素的簡便方法。下面的代碼示例與前面的示例是等效的。
範例:
it = _get_iterator(object);
while (!done) {
switch (gst_iterator_next (it, &item)) {
case GST_ITERATOR_OK:
... use/change item here...
/* release item here */
gst_object_unref (item);
break;
case GST_ITERATOR_RESYNC:
/* handle rollback caused by concurrent modification
* of the list here */
...rollback changes to items...
/* resync iterator to start again */
gst_iterator_resync (it);
break;
case GST_ITERATOR_DONE:
done = TRUE;
break;
}
}
gst_iterator_free (it);