我需要一个支持索引的FIFO结构。每个元素都是一个数据数组,保存在我读取的设备之外。 FIFO具有恒定的大小,并且在启动时每个元素都被清零。
这里有一些伪代码可以帮助您理解这个问题:
UPDATE student SET userRole = 3
请注意,在线程B中不应该更改FIFO,调用者应该只获得一个副本,因此pop不具有破坏性的数据结构在没有中间副本的情况下不一定有效。
我的代码也有一个boost依赖项,我在其他地方使用了一个无锁的spsc_queue。话虽如此,我不知道这个队列在这里对我有用,因为在某些情况下需要作为LIFO工作,并且有时需要记忆整个FIFO。
我也考虑过简单的Thread A (Device Reader):
1. Lock the structure.
2. Pop oldest element off of FIFO (don't need it).
3. Read next array of data (note this is a fixed size array) from the device.
4. Push new data array onto the FIFO.
5. Unlock.
Thread B (Data Request From Caller):
1. Lock the structure.
2. Determine request type.
3. if (request = one array) memcpy over the latest array saved (LIFO).
4. else memcpy over the whole FIFO to the user as a giant array (caller uses arrays).
5. Unlock.
,但是当我不断地推动和弹出时,我担心表现。
答案 0 :(得分:2)
问题中不明确的一点是编译器目标,无论解决方案是否仅限于部分C ++ 11支持(如VS2012),还是完全支持(如VS2015)。你提到了boost依赖,它为旧的编译器提供了类似的功能,所以我会依赖于它,并且假设boost可以提供C ++ 11前编译器可能没有的东西,或者你可以选择C ++ 11的功能类似于现在标准化的互斥锁,锁,线程和shared_ptr。
毫无疑问,我认为FIFO的主要工具(正如您所说,可能偶尔需要LIFO操作)是std::deque
。尽管deque支持合理有效的动态扩展和存储缩小,但与静态大小的主要要求相反,它的主要特征是能够以FIFO和LIFO的方式运行,具有良好的性能,矢量可以&#39 ; t易于管理。在内部,大多数实现提供了可以被类比为由deque编组的较小向量的集合,以便像单个向量容器(用于下标)一样工作,同时允许双端推送和弹出有效的存储器管理。使用矢量,使用循环缓冲技术来固定尺寸可能很诱人,但任何性能改进都是最小的,并且已知deque是可靠的。
关于破坏性流行音乐的观点对我来说并不完全清楚。这可能意味着几件事。 std::deque
提供了back
和front
,以查看双端队列末端的内容,而不会发生破坏。事实上,他们需要查看,因为deque的pop_front
和pop_back
只删除元素,他们不提供对弹出元素的访问权限。在std::deque
上取一个元素并弹出它是一个两步过程。然而,另一个意思是,只读请求者需要严格地作为导航手段,而不是破坏,这不是真正的流行,而是遍历。只要结构处于锁定状态,就可以使用迭代器或索引轻松管理。或者,它也可能意味着您需要队列的独立副本。
假设某个结构代表设备数据:
struct DevDat { .... };
我是否立即面临这个奇怪的问题,这不应该是一个通用的解决方案吗?这对于讨论来说并不重要,但似乎意图是应用程序特定操作和通用线程安全堆栈"机器"的奇怪组合,所以我建议一个通用解决方案,这是很容易翻译的(也就是说,我建议模板类,但如果愿意,你可以轻松选择非模板)。这些伪代码示例很稀疏,只是说明了容器布局思想和提出的概念。
class SafeStackBase
{ protected: std::mutex sync;
};
template <typename Element>
class SafeStack : public SafeStackBase
{ public:
typedef std::deque< Element > DeQue;
private:
DeQue que;
};
SafeStack可以处理堆栈中的任何类型的数据,因此详细信息留给了Element声明,我用typedef说明:
typedef std::vector< DevDat > DevArray;
typedef std::shared_ptr< DevArray > DevArrayPtr;
typedef SafeStack< DevArrayPtr > DeviceQue;
注意我提出向量而不是数组因为我不喜欢选择固定大小的想法,但显然std :: array是一个选项。
SafeStackBase适用于不了解用户数据类型的代码和数据,这就是互斥锁存储在那里的原因。它可以很容易地成为模板类的一部分,但是在非模板库中放置非类型感知数据和代码的做法有助于在可能的情况下减少代码膨胀(例如,不使用Element的函数不需要在模板实例中扩展)。我建议使用DevArrayPtr,这样阵列可以被拔出&#34;没有复制数组的队列,然后在shared_ptr的共享所有权下共享和分布在结构之外。这是一个例子,并没有充分处理有关这些阵列内容的问题。这可以由DevDat管理,它可以编组读取数组数据,同时限制将数组数据写入授权的朋友(写访问器策略),这样线程B(仅限读取器)不会随意修改内容。通过这种方式,可以在不复制数据的情况下提供这些数组。只需返回DevArrayPtr的副本,以便对整个数组进行公共访问。这也支持返回DevArrayPtr的容器,支持ThreadB第4点(将整个FIFO复制给用户),如:
typedef std::vector< DevArrayPtr > QueArrayVec;
typedef std::deque< DevArrayPtr > QueArrayDeque;
typedef std::array< DevArrayPtr, 12 > QueArrays;
关键是你可以返回你喜欢的任何容器,这只是一个指向内部std::array< DevDat >
的指针数组,让DevDat
通过要求一些授权对象进行写入来控制读/写授权,如果此副本应作为FIFO运行而不会对线程A的写入所有权造成潜在干扰,QueArrayDeque
将完整的功能集提供为独立的FIFO / LIFO结构。
这引出了关于线程A的观察。你说状态锁定是步骤1,而解锁是步骤5,但是我提交锁定时确实只需要步骤2和4。第3步可能需要一些时间,即使你认为这是一个很短的时间,它也不会像弹出后跟推动一样短。关键在于锁实际上是关于控制FIFO / LIFO队列结构,而不是关于从设备读取数据。因此,该数据可以被构造成DevArray,然后提供给SafeStack以在弹出/推送锁定。
假设SafeStack中的代码:
typedef std::lock_guard< std::mutex > Lock; // I use typedefs a lot
void StuffIt( const Element & e )
{ Lock l( sync );
que.pop_front();
que.push_back( e );
}
StuffIt
做了一个简单,通用的工作,弹出前方,推动后方,锁定。由于它需要const Element &
,因此线程A的第3步已经完成。因为我建议Element是DevArrayPtr,所以它用于:
DeviceQue dq;
auto p = std::make_shared<DevArray>();
dq.StuffIt( p );
如何填充DevArray
取决于它的构造函数或某个函数,重点是shared_ptr
用于传输它。
这提出了一个关于SafeStack的更通用的观点。显然,标准访问功能有一些可能性,它们可以模仿std :: deque,但SafeStack的主要工作是锁定/解锁访问控制,并在锁定时执行某些操作。为此,我提交一个通用仿函数足以概括这个概念。首选的机制,尤其是关于boost的机制,取决于你,但是类似于(SafeStack中的代码):
bool LockedFunc( std::function< bool(DevQue &)> f )
{
Lock l( sync );
f( que );
}
或者您喜欢使用DevQue作为参数来调用函子的任何机制。这意味着您可以在锁定时完全访问deque(及其界面),或者提供在锁定下执行特定任务的仿函数或lambda。
设计要点是使SafeStack变小,专注于在锁定下执行一些操作的最小任务,将大多数任何类型的数据放入队列中。然后,使用最后一点,在shared_ptr下提供数组,以提供线程B步骤3和4的服务。
要清楚这一点,请记住,对shared_ptr进行的任何操作都要复制它,这与简单的POD类型(如整数)相对于容器可以做的类似。也就是说,可以循环遍历DevQue的元素,将这些元素的副本转换为同一代码中的另一个容器,这样就可以对整数容器执行此操作(请记住,它是模板的成员函数 - 该类型是通用的)。由此产生的工作只是复制指针,这比复制整个数据数组要少。
现在,第4步对我来说并不清楚。它似乎表示您需要返回DevArray,它是队列中所有条目的累积内容。安排很简单,但它可能会更好地使用矢量(因为它可以动态扩展),但只要std :: array有足够的空间,它肯定是可能的
然而,这种数组与队列本机数组之间唯一真正的区别是#34;是如何遍历(和计数)。返回一个元素(步骤3)很快,但由于步骤4是在锁定状态下指示的,所以如果他们不必这样做,那么大多数锁定函数应该真正做到。
我建议SafeStack应该能够提供快速的que(DeQue typedef)副本。然后,在锁定之外,线程B有一个DeQue的副本(一个std :: deque&lt; DevArrayPtr&gt;)来形成它自己的巨型数组&#34;。
现在,更多关于那个数组。到目前为止,我没有充分处理它的编组问题。我刚刚建议DevDat这样做,但这可能不够。当然可以编写传递DevDats集合的std :: array或std :: vector的内容。也许这应该是它自己的外部结构。我将此留给您,因为我已经说过,SafeStack现在专注于它的小任务(锁定/访问/解锁),并且可以采取任何可以由share_ptr(或POD&#39;和可复制对象)。以同样的方式,SafeStack是一个外壳,用一个互斥体编组std :: deque,一些类似的外壳可以编组对devDats的std :: vector或std :: array的只读访问,并使用一种写访问器线程A.这可能是一个简单的东西,只允许构造std :: array来创建它的内容,之后只读访问可以是所有提供的。
答案 1 :(得分:1)
我建议你使用boost::circular_buffer
这是一个固定大小的容器,支持随机访问迭代,在开始和结束时插入和擦除常量时间。您可以将其用作push_back()
的FIFO,读取back()
以获取保存的最新数据,并通过begin(), end()
或operator[]
遍历整个容器。
但是在启动时,元素不会被清零。在我看来,它具有更方便的界面。容器最初是空的,插入将增加尺寸,直到达到最大尺寸。