返回信息流我就来开个新帖
VC有原子操作声明符吗 或者说保证原子操作的机制
C11有 atomic类型 然而并不支持
最近项目代码里不太适用锁 倒是很希望能用简单的原子操作
这是一条镜像帖。来源:北邮人论坛 / cpp / #89754同步于 2015/12/31
该镜像源已超过 30 天没有更新,可能在源站已被删除。
CPP机器人发帖
[问题]好久没有新帖了
FromMars
2015/12/31镜像同步18 回复
订阅后,新回复会通过你的通知中心匿名送达。
9 条回复
现在是
一个线程只对 queue做push
另一取线程对 queue做pop
这种简单的生产消费模型
即使不加任何锁或者原子操作保证 应该也不会对数据造成损坏吧
【 在 FromMars 的大作中提到: 】
: 现在是
: 一个线程只对 queue做push
: 另一取线程对 queue做pop
: ...................
会。只要在没有使用锁或者原子操作的情况下,对任何一个变量进行修改,机器都会冒烟。如果是队列的话,起码队首、队尾指针(如果是数组实现)或者元素之间的链接(如果是链表实现)都要并发地读写的。即使都是原子的,如果memory order不对,也会出现意外的事情。
一个简单的例子:可以把x看成一个只有一个元素的队列。
#include <atomic>
#include <thread>
#include <cstdio>
using namespace std;
class Foo {
public:
int a;
};
atomic<Foo*> x(nullptr);
void f() {
Foo *y = new Foo;
y->a = 10;
x.store(y, memory_order_relaxed);
}
void g() {
Foo *y;
while(true) {
y = x.load(memory_order_relaxed);
if (y != nullptr) {
break;
}
}
int b = y->a;
printf("b = %d\n", b); // may not be 10
}
int main() {
thread t1(f);
thread t2(g);
t1.join();
t2.join();
return 0;
}
出问题的原因是:g看到x里面存的指针被更新了,并不意味着它同时也能看到指针指向的内容被更新了。而一般来说,队列的用处是一个线程处理完一件事,用队列把数据传送给另一个线程,希望对方能够看到内容,或者其他副作用。
按MSDN的说法,vc2012就已经支持atomic头文件了。 https://msdn.microsoft.com/en-au/library/hh874894.aspx
还有一个库,叫libatomic_op,一个跨平台的原子操作支持库。这个库出现得比C++11还早。但我觉得这个年代应该鼓励用C++11的标准API。 https://github.com/ivmai/libatomic_ops/wiki/Download
取线程pop之前会先判断queue的size ,有元素才会执行pop,如果队列是空的话,不进行
具体的过程是:
1.队列size==0,取线程不进行操作,首指针没被改变,写线程可以写入,尾指针只被写线程操作改变;
2.队列size>0, 取线程可读取,并且pop一个元素,头指针下移,写线程可以写入,尾指针下移;
3.特别的按照暖神所说, 当size==0的时候,首尾指针指向一个地址,写线程写入一个元素的时候,噢,时刻,元素刚刚好写入,并且在写入完全之后,尾指针下移,才会使size++变成1,即使现在取线程再进行pop,已经影响不了尾指针了吧
这是我的猜想,并且size++和--应该是原子操作,不会影响结果
我想要原子操作并不是用在队列上,而是用某个aotmic<bool> m_queIsEntry来判断队列是否正在被操作(push或者pop),如果某个线程需要操作队列的话,先判断该变量m_queIsEntry,如果为false,把它置为true,再进行队列的操作,如果为true,则不进行队列操作;
不过貌似没什么用
【 在 nuanyangyang 的大作中提到: 】
: 会。只要在没有使用锁或者原子操作的情况下,对任何一个变量进行修改,机器都会冒烟。如果是队列的话,起码队首、队尾指针(如果是数组实现)或者元素之间的链接(如果是链表实现)都要并发地读写的。即使都是原子的,如果memory order不对,也会出现意外的事情。
:
: 一个简单的例子:可以把x看成一个只有一个元素的队列。
: ...................
就是先在这个项目是VC老年平台做的……
正在考虑迁移到VS2013,支持C11就不用想那么多
【 在 nuanyangyang 的大作中提到: 】
: 按MSDN的说法,vc2012就已经支持atomic头文件了。 https://msdn.microsoft.com/en-au/library/hh874894.aspx
: 还有一个库,叫libatomic_op,一个跨平台的原子操作支持库。这个库出现得比C++11还早。但我觉得这个年代应该鼓励用C++11的标准API。 https://github.com/ivmai/libatomic_ops/wiki/Download
这样说也看不出问题。问题都出在实现细节上。要不要试着实现一个简单的队列?
【 在 FromMars 的大作中提到: 】
: 取线程pop之前会先判断queue的size ,有元素才会执行pop,如果队列是空的话,不进行
: 具体的过程是:
: 1.队列size==0,取进程不进行操作,首指针没被改变,写进程可以写入,尾指针只被写进程操作改变;
: ...................
很久之前看过C语言里对队列的链式实现源码
当然那个 .size()操作是我自己YY的 具体不知道是
遍历链表得出 还是用一个数据记录元素个数 亦或者是(指针-尾指针)
细节上只要保证元素加入到队列里面之后 .size()才会 +1就行
【 在 nuanyangyang 的大作中提到: 】
: 这样说也看不出问题。问题都出在实现细节上。要不要试着实现一个简单的队列?
:
单一生产者消费者,无锁队列 大概伪码:
struct queue {
unsigned int IN;
unsigned int OUT;
unsigned int SIZE; /* must be power of 2 */
unsigned char *BUFFER;
};
unsigned int queue_in(unsigned char *buf, unsigned int len)
{
unsigned int t;
len = min(len, SIZE - (IN - OUT));
t = min(len, SIZE - (IN & (SIZE - 1));
memcpy(BUFFER + (IN & (SIZE - 1)), buf, t);
memcpy(BUFFER, buf + t, len - t);
IN += len;
return len;
}
unsigned int queue_out(unsigned char *buf, unsigned int len)
{
unsigned int t;
len = min(len, IN - OUT);
t = min(len, SIZE - (OUT & (SIZE - 1));
memcpy(buf, BUFFER + (OUT & (SIZE - 1)), t);
memcpy(buf + t, BUFFER, len - t);
OUT += len;
return len;
}
不用质疑什么IN大于OUT,IN小于OUT,unsigned int溢出之类的
酒精考验的Linux kernel kfifo就长这个样子~