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文件中。需要持有锁的公共属性用标记/*< public >*/ /* with <lock_type> */,在<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);