首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >对STL容器的安全并行只读访问

对STL容器的安全并行只读访问
EN

Stack Overflow用户
提问于 2012-05-31 12:21:14
回答 2查看 4.8K关注 0票数 21

我希望从运行线程的并行访问基于STL的容器只读。不使用任何用户实现的锁定。以下代码的基础是C++11,并适当地实现了该标准。

http://gcc.gnu.org/onlinedocs/libstdc++/manual/using_concurrency.html

http://www.sgi.com/tech/stl/thread_safety.html

http://www.hpl.hp.com/personal/Hans_Boehm/c++mm/threadsintro.html

http://www.open-std.org/jtc1/sc22/wg21/ (current draftN3337,本质上是带有小错误和更正排字的C++11 )

23.2.2容器数据竞争container.requirements.dataraces

为了避免数据竞争(17.6.5.9),实现应考虑以下功能为const: begin、end、rbegin、rend、front、back、rend、lower_bound、upper_bound、equal_range、at和(除关联或无序关联容器之外) operator[]。

尽管如此(17.6.5.9),当同一序列中的不同元素(除向量外)的内容被同时修改时,需要实现避免数据竞争。

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

17.6.5.9数据竞争避免res.on.data.races 1本节指定实现应满足的防止数据竞争的要求(1.10)。除另有规定外,每项标准图书馆功能均须符合每项规定。在以下指定的情况下,实现可以防止数据竞争。

2 C++标准库函数不应直接或间接地访问当前线程以外的线程访问的对象(1.10),除非通过函数的讨论直接或间接访问对象,包括此。

3 C++标准库函数不得直接或间接修改当前线程以外的线程可访问的对象(1.10),除非通过函数的非const参数直接或间接访问对象,包括此参数。

注:例如,这意味着在没有同步的情况下,实现不能将静态对象用于内部目的,因为即使在线程之间没有显式共享对象的程序中,它也可能导致数据竞争。-尾注

5 C++标准库函数不得通过其参数或其容器参数的元素间接访问对象,除非调用其对这些容器元素的规范所要求的函数。

6调用标准库容器或字符串成员函数获得的迭代器上的操作可能

访问基础容器,但不应修改它。注意:特别是,使迭代器无效的容器操作与与该容器关联的迭代器上的操作冲突。-尾注

7实现可以在线程之间共享自己的内部对象,如果对象对用户不可见,并且不受数据竞争的影响。

8除非另有规定,C++标准库函数应仅在当前线程内执行所有操作,如果这些操作对用户是可见的(1.10)。

注:这允许实现并行操作,如果没有可见的副作用。-尾注

结论

容器不是线程安全的!但是在来自多个并行线程的容器上调用const函数是安全的。因此,可以从并行线程执行只读操作,而无需锁定。我说的对吗?

我假装它们不存在任何错误的实现,而且C++11标准的每个实现都是正确的。

示例:

代码语言:javascript
复制
// concurrent thread access to a stl container
// g++ -std=gnu++11 -o p_read p_read.cpp -pthread -Wall -pedantic && ./p_read
#include <iostream>
#include <iomanip>
#include <string>
#include <unistd.h>

#include <thread>
#include <mutex>

#include <map>

#include <cstdlib>
#include <ctime>
using namespace std;

// new in C++11
using str_map = map<string, string>;

// thread is new in C++11
// to_string() is new in C++11

mutex m;
const unsigned int MAP_SIZE = 10000;

void fill_map(str_map& store) {
    int key_nr;
    string mapped_value;
    string key;

    while (store.size() < MAP_SIZE) {
        // 0 - 9999
        key_nr = rand() % MAP_SIZE;

        // convert number to string
        mapped_value = to_string(key_nr);
        key = "key_" + mapped_value;

        pair<string, string> value(key, mapped_value);
        store.insert(value);
    }
}

void print_map(const str_map& store) {
    str_map::const_iterator it = store.begin();

    while (it != store.end()) {
        pair<string, string> value = *it;
        cout << left << setw(10) << value.first << right << setw(5) << value.second << "\n";
        it++;   
    }
}

void search_map(const str_map& store, int thread_nr) {
    m.lock();
    cout << "thread(" << thread_nr << ") launched\n";
    m.unlock();

    // use a straight search or poke around random
    bool straight = false;
    if ((thread_nr % 2) == 0) {
        straight = true;
    }

    int key_nr;
    string mapped_value;
    string key;
    str_map::const_iterator it;

    string first;
    string second;

    for (unsigned int i = 0; i < MAP_SIZE; i++) {

        if (straight) {
            key_nr = i;
        } else {
            // 0 - 9999, rand is not thread-safe, nrand48 is an alternative             
            m.lock();
            key_nr = rand() % MAP_SIZE;
            m.unlock();
        }

        // convert number to string
        mapped_value = to_string(key_nr);
        key = "key_" + mapped_value;

        it = store.find(key);

        // check result
        if (it != store.end()) {
            // pair
            first = it->first;
            second = it->second;

            // m.lock();
            // cout << "thread(" << thread_nr << ") " << key << ": "
            //      << right << setw(10) << first << setw(5) << second << "\n"; 
            // m.unlock();

            // check mismatch
            if (key != first || mapped_value != second) {
                m.lock();
                cerr << key << ": " << first << second << "\n"
                     << "Mismatch in thread(" << thread_nr << ")!\n";
                exit(1);

                // never reached
                m.unlock();
            }
        } else {
            m.lock();
            cerr << "Warning: key(" << key << ") not found in thread("
                 << thread_nr << ")\n";
            exit(1);

            // never reached
            m.unlock();
        }
    }
}

int main() {
    clock_t start, end;
    start = clock();

    str_map store;
    srand(0);

    fill_map(store);
    cout << "fill_map finished\n";

    // print_map(store);
    // cout << "print_map finished\n";

    // copy for check
    str_map copy_store = store;

    // launch threads
    thread t[10];
    for (int i = 0; i < 10; i++) {
        t[i] = thread(search_map, store, i);
    }

    // wait for finish
    for (int i = 0; i < 10; i++) {
        t[i].join();
    }
    cout << "search_map threads finished\n";

    if (store == copy_store) {
        cout << "equal\n";
    } else {
        cout << "not equal\n";
    }


    end = clock();
    cout << "CLOCKS_PER_SEC " << CLOCKS_PER_SEC << "\n";
    cout << "CPU-TIME START " << start << "\n";
    cout << "CPU-TIME END " << end << "\n";
    cout << "CPU-TIME END - START " << end - start << "\n";
    cout << "TIME(SEC) " << static_cast<double>(end - start) / CLOCKS_PER_SEC << "\n";

    return 0;
}

这个代码可以用GCC 4.7编译,在我的机器上运行得很好。

回波$?

$0

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2012-05-31 12:25:42

来自1.10/4和1.10/21节中的C++11规范的数据争用要求至少两个具有非原子访问相同内存位置集的线程,这两个线程在访问内存位置集方面不同步,并且至少有一个线程写入到或修改一组内存位置中的元素。所以在你的情况下,如果线程只是在读,你就会没事.根据定义,由于没有一个线程写入同一组内存位置,因此即使线程之间没有显式同步机制,也不存在数据竞争。

票数 21
EN

Stack Overflow用户

发布于 2012-05-31 12:26:26

是的,你是对的。只要填充向量的线程在读取器线程启动之前完成,您就安全了。最近有a similar question

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/10833512

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档