Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/130.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/multithreading/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 在无序映射(C+;+;)中并发写入不同的存储桶?_C++_Multithreading_C++11_Concurrency - Fatal编程技术网

C++ 在无序映射(C+;+;)中并发写入不同的存储桶?

C++ 在无序映射(C+;+;)中并发写入不同的存储桶?,c++,multithreading,c++11,concurrency,C++,Multithreading,C++11,Concurrency,这里是C++新手。我试图在无序的映射中同时写入不同的存储桶。从搜索中可以看出,我的理解是,这应该是一个线程安全的操作。我(可能不正确)的理解基于答案和C++11标准的参考部分(特别是第2项——我的重点): 23.2.2容器数据竞赛[容器.要求.数据竞赛] < P > 1为了避免数据竞争(17.5.9),实现应考虑以下功能:起始、结束、RRADE、RADE、前部、后部、数据、查找、下限、上限、均等范围、AT和除关联或无序的关联容器之外,运算符[]、 2尽管有(17.6.5.9)的规定,但当同时修改

这里是C++新手。我试图在无序的映射中同时写入不同的存储桶。从搜索中可以看出,我的理解是,这应该是一个线程安全的操作。我(可能不正确)的理解基于答案和C++11标准的参考部分(特别是第2项——我的重点):

23.2.2容器数据竞赛[容器.要求.数据竞赛]

< P > 1为了避免数据竞争(17.5.9),实现应考虑以下功能:起始、结束、RRADE、RADE、前部、后部、数据、查找、下限、上限、均等范围、AT和除关联或无序的关联容器之外,运算符[]、

2尽管有(17.6.5.9)的规定,但当同时修改同一序列中不同元素(向量除外)中包含的对象的内容时,实现需要避免数据竞争

3[注:对于大小大于1的向量x,x[1]=5和*x.begin()=10可以在没有数据竞争的情况下同时执行,但是x[0]=5和*x.begin()=10同时执行可能会导致数据竞争。作为一般规则的例外,对于向量y,y[0]=true可能与y[1]=true竞争。-结束说明]

在任何情况下,对于标准容器,向不同的bucket写入似乎都不是线程安全的,如下代码所示。您将看到,我在写入之前启用了一个与修改的bucket对应的锁,但有时对并没有被正确记录。无论如何,如果我使用一个锁——例如,只需更改
auto bkt=mm->bucket(key)
自动bkt=0,有效地锁定了整个无序的映射容器——一切都按预期工作

#include <iostream>
#include <unordered_map>
#include <atomic>
#include <vector>
#include <thread>

#define NUM_LOCKS 409
#define N 100
#define NUM_THREADS 2

using namespace std;


class SpinLock
{
    public:
        void lock()
        {
            while(lck.test_and_set(memory_order_acquire)){}
        }
    void unlock()
        {
            lck.clear(memory_order_release);
        }

    private:
        atomic_flag lck = ATOMIC_FLAG_INIT;
};


vector<SpinLock> spinLocks(NUM_LOCKS);


void add_to_map(unordered_map<int,int> * mm, const int keyStart, const int keyEnd, const int tid){

    for(int key=keyStart;key<keyEnd;++key){
        auto bkt = mm->bucket(key);

        //lock bucket
        spinLocks[bkt].lock();

        //insert pair
        mm->insert({key,tid});

        //unlock bucket
        spinLocks[bkt].unlock();
    }

}


int main() {

    int Nbefore, Nafter;
    thread *t = new thread[NUM_THREADS];

    //create an unordered map, and reserve enough space to avoid a rehash
    unordered_map<int,int> my_map;
    my_map.reserve(2*NUM_THREADS*N);

    //count number of buckets to make sure that a rehash didn't occur
    Nbefore=my_map.bucket_count();


    // Launch NUM_THREADS threads.  Thread k adds keys k*N through (k+1)*N-1 to the hash table, all with associated value = k.

    for(int threadID=0;threadID<NUM_THREADS;++threadID){
        t[threadID]=thread(add_to_map,&my_map,threadID*N,(threadID+1)*N,threadID);
    }

    // Wait for the threads to finish
    for(int threadID=0;threadID<NUM_THREADS;++threadID){
        t[threadID].join();
    }

    //count number of buckets to make sure that a rehash didn't occur
    Nafter=my_map.bucket_count();


    cout << "Number of buckets before adding elements: " << Nbefore <<endl;
    cout << "Number of buckets after  adding elements: " << Nafter  << " <--- same as above, so rehash didn't occur" <<endl;

    //see if any keys are missing
    for(int key=0;key<NUM_THREADS*N;++key){

        if(!my_map.count(key)){

            cout << "key " << key << " not found!" << endl;

        }
    }

    return 0;
}
#包括
#包括
#包括
#包括
#包括
#定义NUM_锁409
#定义N 100
#定义NUM_线程2
使用名称空间std;
类自旋锁
{
公众:
无效锁()
{
while(lck.test_和_set(memory_order_acquire)){
}
无效解锁()
{
lck.清除(内存、命令和释放);
}
私人:
原子标志lck=原子标志初始化;
};
向量自旋锁(NUM_锁);
void add_to_map(无序_map*mm,常量int keyStart,常量int keyEnd,常量int tid){
for(int key=keyStart;keybucket(key);
//锁斗
自旋锁[bkt].lock();
//插入对
mm->插入({key,tid});
//解锁铲斗
自旋锁[bkt]。解锁();
}
}
int main(){
内恩贝弗尔,纳弗特;
thread*t=新线程[NUM_THREADS];
//创建一个无序的映射,并保留足够的空间以避免再次刷新
无序地图我的地图;
我的映射保留(2*NUM\u线程*N);
//计算桶的数量,以确保不会发生再次灰化
Nbefore=my_map.bucket_count();
//启动NUM_THREADS THREADS。线程k将键k*N到(k+1)*N-1添加到哈希表中,所有键的关联值均为k。

对于(int-threadID=0;threadID,容器的元素不是bucket,而是
value\u type
元素

修改
std
容器中的一个元素不会对其他元素产生并发影响,但修改一个bucket并不能保证这一点


添加或删除存储桶中的元素是容器上的非
const
操作,该操作不在非
const
操作的特殊列表中,无需同步即可安全使用。

这是提问的正确方式!研究、代码、错误以及您如何修复它!大多数新用户应该阅读此内容。+1当然,正如您可能已经了解到的,您的代码有UB,因为您对
insert
的调用会导致数据竞争。在实际实现中,一个简单的失败模式是在libc++中,其中
insert
会增加大小计数,因此您在大小变量上存在竞争。但只要看看您自己的库实现,就会注意它不是线程安全的方式。我同意@KerrekSB的评论,即在这种情况下,
insert
会导致UB(激发原始问题)。但是,我不同意Ron过于简单的说法,即写入不是线程安全的。Ron忽略了引用标准的2和3。该标准要求在同时修改容器的不同元素时避免争用。因此,如果线程同步以不在同一位置同时写入,则写入是安全的。因此看起来一个桶接一个桶的锁就足够了,但显然不行。@KerrekSB,我如何看待libc++实现?添加元素与修改元素不同。桶不是无序集合/映射的元素——元素是键/值对。你在添加元素,我看不到这方面的保证ng safe?@Tom:我认为你误解了标准中的陈述。正如Yakk所解释的,意思是你可以同时修改不同的容器元素。你永远不允许同时修改容器本身,“修改”意味着调用任何非常量成员函数(见Ron的评论).您可以整天对实施情况进行假设,但这并不能为您购买任何标准保证。
Number of buckets before adding elements: 401
Number of buckets after  adding elements: 401 <--- same as above, so rehash didn't occur
key 0 not found!
key 91 not found!
key 96 not found!
key 97 not found!
key 101 not found!
key 192 not found!
key 193 not found!
key 195 not found!