我正在阅读关于使用bool进行线程控制的问题,并且被@eran的这个答案所吸引:
使用volatile仅在单核上足够,其中所有线程使用相同的缓存.在多核上,如果在一个核上调用stop()而在另一个核上执行run(),则CPU缓存可能需要一些时间来进行同步,这意味着两个核可能会看到两个不同的isRunning_视图.
如果您使用同步机制,它们将确保所有缓存获得相同的值,代价是暂停程序一段时间.性能或正确性对您来说更重要取决于您的实际需求.
我花了一个多小时搜索一些声明,说同步原语强制缓存一致但失败了.我最接近的是维基百科:
关键字volatile不保证内存屏障可以强制执行缓存一致性.
这表明内存屏障确实强制缓存一致性,并且由于一些同步原语是使用内存屏障(同样来自维基百科)实现的,这是一些"证据".
但我不知道是否相信这一点与否,并确保我不会误解它.
有人可以澄清一下吗?
我已经看到多个代码实例,其中使用 && 表示法声明了函数参数包,如下所示,但我看不出使用这种表示法有什么好处。
template<typename... Args>
void Function(Args... args)
{
}
template<typename... Args>
void Function(Args&&... args)
{
}
Run Code Online (Sandbox Code Playgroud)
我的第一个想法是 && 形式将专门用于 r 值对象,但这个测试证明是错误的:
struct Object
{
// Added bodies so I see what is being called via a step-into
Object() {}
Object(const Object&) {}
Object(Object&&) noexcept {}
Object& operator=(const Object&) { return *this; }
Object& operator=(Object&&) noexcept { return *this; }
};
Object GetObject() { Object o; return o; }
Object obj;
Function(GetObject());
Function(GetObject());
Run Code Online (Sandbox Code Playgroud)
在这里,VS 2017 抱怨该函数的两种形式都是调用的可行候选者。
有人能解释一下这两者之间的区别吗,一个可能比另一个有什么优势?
我正在试验IPv6套接字,特别是Windows Vista及更高版本提供的"双栈"功能,默认情况下显然在Unix上.我发现当我将服务器绑定到特定的IP地址或本地计算机的主机名解析时,我无法接受来自IPv4客户端的连接.但是当我绑定到INADDR_ANY时,我可以.
请考虑我的服务器的以下代码.您可以看到我遵循Microsoft的建议创建IPv6套接字,然后将IPV6_V6ONLY标志设置为零:
addrinfo* result, *pCurrent, hints;
memset(&hints, 0, sizeof hints); // Must do this!
hints.ai_family = AF_INET6;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE; // We intend to use the addrinfo in a call to connect(). (I know it is ignored if we specify a server to connect to...)
int nRet = getaddrinfo("powerhouse", "82", &hints, &result);
SOCKET sock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
int no = 0;
if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&no, sizeof(no)) != 0)
return -1;
if (bind(sock, result->ai_addr, result->ai_addrlen) …Run Code Online (Sandbox Code Playgroud) (注意:我已经根据我认为人们可能会提供帮助的地方添加了这个问题的标签,所以请不要大喊:))
在我的VS 2017 64bit项目中,我有一个32位长的值m_lClosed.当我想更新它时,我使用了Interlocked一系列函数.
考虑这个代码,在线程#1上执行
LONG lRet = InterlockedCompareExchange(&m_lClosed, 1, 0); // Set m_lClosed to 1 provided it's currently 0
Run Code Online (Sandbox Code Playgroud)
现在考虑这个代码,在线程#2上执行:
if (m_lClosed) // Do something
Run Code Online (Sandbox Code Playgroud)
我理解在单个CPU上,这不会是一个问题,因为更新是原子的,读取也是原子的(参见MSDN),因此线程抢占不能使变量处于部分更新状态.但是在多核CPU上,如果每个线程都在不同的CPU上,我们真的可以让这两段代码并行执行.在这个例子中,我认为这不会是一个问题,但是在测试可能正在更新的过程中仍然感觉不对.
这个网页告诉我,多个CPU的原子性是通过LOCK汇编指令实现的,防止其他CPU访问该内存.这听起来像我需要的,但上面为if测试生成的汇编语言仅仅是
cmp dword ptr [l],0
Run Code Online (Sandbox Code Playgroud)
......没有任何LOCK指示.
在这样的事件中我们应该如何确保读取的原子性?
编辑24/4/18
首先感谢这个问题产生的所有兴趣.我在下面显示实际代码; 我故意把它简单地集中在它的所有原子性上,但显然如果我从一分钟那里展示它就会更好.
其次,实际代码所在的项目是VS2005项目; 因此无法访问C++ 11原子.这就是我没有在问题中添加C++ 11标签的原因.我正在使用VS2017进行"刮擦"项目,以便在我学习的时候每次做出改变时都要建立一个巨大的VS2005.另外,它是一个更好的IDE.
是的,所以实际代码存在于IOCP驱动的服务器中,这整个原子性是关于处理一个封闭的套接字:
class CConnection
{
//...
DWORD PostWSARecv()
{
if (!m_lClosed)
return ::WSARecv(...);
else
return WSAESHUTDOWN;
}
bool SetClosed()
{
LONG lRet = InterlockedCompareExchange(&m_lClosed, 1, 0); // Set …Run Code Online (Sandbox Code Playgroud) 在 Visual Studio 上编译如下:
\ntemplate<typename ArgType, typename ReturnType>\nstruct Test\n{\n using FunctionPointerType = std::conditional_t<\n std::is_same_v<ArgType, void>\n , ReturnType(*)()\n , ReturnType(*)(ArgType)\n >;\n FunctionPointerType Func;\n};\n\nint main()\n{\n Test<void, char> tt;\n}\nRun Code Online (Sandbox Code Playgroud)\n但不能在 Linux g++ 上编译。我得到的错误是
\nerror : invalid parameter type \xe2\x80\x98void\xe2\x80\x99\nRun Code Online (Sandbox Code Playgroud)\n我知道我不能在模板中使用 void,这就是我使用std::conditional_t和 的原因std::is_same_v。
我看不出什么是不正确的,有人可以告诉我吗?
\n以下代码是对象包装器的简化版本。我希望能够Object无缝访问底层,即不需要括号,如评论所述:
struct A
{
void Func() {}
};
template <typename Object>
struct ObjectWrapper
{
ObjectWrapper(Object& o) : object_(&o) {}
operator Object& () { return *object_; }
Object& operator ()() { return *object_; }
Object* object_;
};
int main()
{
A a;
ObjectWrapper<A> obj(a);
//
// Trying to call Func() on the A object that 'obj' wraps...
//
obj.operator A& ().Func(); // Messy
obj().Func(); // Better but still has parentheses
// I really want to be able to say …Run Code Online (Sandbox Code Playgroud) 我正在试验我的Linux机器上的TCP保持活动,并编写了以下小型服务器:
#include <iostream>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h> // inet_ntop
#include <netinet/tcp.h>
#include <netdb.h> // addrinfo stuff
using namespace std;
typedef int SOCKET;
int main(int argc, char *argv [])
{
struct sockaddr_in sockaddr_IPv4;
memset(&sockaddr_IPv4, 0, sizeof(struct sockaddr_in));
sockaddr_IPv4.sin_family = AF_INET;
sockaddr_IPv4.sin_port = htons(58080);
if (inet_pton(AF_INET, "10.6.186.24", &sockaddr_IPv4.sin_addr) != 1)
return -1;
SOCKET serverSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (bind(serverSock, (sockaddr*)&sockaddr_IPv4, sizeof(sockaddr_IPv4)) != 0 || listen(serverSock, SOMAXCONN) != 0)
{
cout << "Failed to setup listening socket!\n";
}
SOCKET clientSock …Run Code Online (Sandbox Code Playgroud) 我正在实现我自己的原子类,因为在我无法访问C++ 11原子库的特定项目中.到目前为止,我有以下代码:
class CAtomicLong
{
public:
CAtomicLong(long lVal) : m_lValue(lVal) {}
long operator+(long lVal)
{
CAutoLock lock(m_lock);
m_lValue += lVal;
return m_lValue;
}
private:
CMyMutex m_lock;
long m_lValue;
};
Run Code Online (Sandbox Code Playgroud)
假设这CMyMutex是一个围绕互斥锁的自定义包装器,并且CAutoLock是一个类,其析构函数解锁在构造期间传递给它的对象.无论如何,这些细节在很大程度上与这个问题无关.
我想知道的是,这样返回是否安全m_lValue; 即它是否会被复制到寄存器中以在lock调用析构函数之前返回?我问,因为我担心撕裂的读取和写入,因为如果在设置返回寄存器之前调用析构函数,则另一个线程可以m_lValue在复制以进行返回时开始修改.
我已经看过Visual Studio中的反汇编这样的代码,它似乎显示了在调用析构函数之前进行的返回调用,但是a)我真的不知道我在使用汇编看到了什么(我我还在学习:))和b)我不知道这是否是标准行为(再次,我还在学习).这个潜在问题最安全的解决方法是
long operator+(long lVal)
{
CAutoLock lock(m_lock);
long lTemp = (m_lValue += lVal);
return lTemp;
}
Run Code Online (Sandbox Code Playgroud)
......但如果这有点矫枉过正我现在就知道了.
从Anger Fog 的 C++ optimization manual,我读到:
如果成员相对于结构或类的开头的偏移量小于 128,则访问数据成员的代码会更加紧凑,因为偏移量可以表示为 8 位有符号数。如果相对于结构或类的开头的偏移量是 128 字节或更多,则偏移量必须表示为 32 位数字(指令集在 8 位和 32 位偏移量之间没有任何内容)。例子:
// Example 7.40
class S2 {
public:
int a[100]; // 400 bytes. first byte at 0, last byte at 399
int b; // 4 bytes. first byte at 400, last byte at 403
int ReadB() {return b;}
};
Run Code Online (Sandbox Code Playgroud)
这里 b 的偏移量为 400。任何通过指针或成员函数(如 ReadB)访问 b 的代码都需要将偏移量编码为 32 位数字。如果 a 和 b 交换,则可以使用编码为 8 位有符号数的偏移量访问两者,或者根本没有偏移量。这使得代码更紧凑,从而更有效地使用代码缓存。因此,建议将大数组和其他大对象放在结构或类声明的最后,最常用的数据成员放在最前面。如果不可能在前 128 个字节中包含所有数据成员,则将最常用的成员放在前 128 个字节中。
我曾尝试这样做,我看到在这个测试程序的组件输出没有什么区别,如图所示在这里: …
这是将IPv4客户端连接到IPv6服务器的延续:连接被拒绝.我正在尝试使用双堆栈套接字并尝试了解哪些setsockopt与IPV6_V6ONLY有用.在链接的问题上,我被告知"如果您还将服务器绑定到IPv6映射的IPv4地址,则将IPV6_V6ONLY设置为0会很有用".我在下面做了这个,并期望我的服务器能够接受来自IPv6和IPv4客户端的连接.但令人震惊的是,当我使用V4和V6插槽运行我的客户端时,两者都无法连接!
有人可以告诉我我做错了什么,还是我误解了IPv6双栈功能?
服务器:
void ConvertToV4MappedAddressIfNeeded(PSOCKADDR pAddr)
{
// if v4 address, convert to v4 mapped v6 address
if (AF_INET == pAddr->sa_family)
{
IN_ADDR In4addr;
SCOPE_ID scope = INETADDR_SCOPE_ID(pAddr);
USHORT port = INETADDR_PORT(pAddr);
In4addr = *(IN_ADDR*)INETADDR_ADDRESS(pAddr);
ZeroMemory(pAddr, sizeof(SOCKADDR_STORAGE));
IN6ADDR_SETV4MAPPED(
(PSOCKADDR_IN6)pAddr,
&In4addr,
scope,
port
);
}
}
addrinfo* result, hints;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
int nRet = getaddrinfo("powerhouse", "82", &hints, &result);
SOCKET sock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
int no …Run Code Online (Sandbox Code Playgroud)