【问题标题】:Reading/Writing from STL Map in multithreaded environment在多线程环境中从 STL Map 读/写
【发布时间】:2010-10-01 04:26:15
【问题描述】:

问题:我需要编写一个函数,该函数从地图中返回输入键的值。如果函数在 map 中找不到值,它将从数据库中获取值,写入 map 以供将来使用并返回相同的值。可以有多个线程调用这个函数。

我在想这条线:

string GetData (const int key)
{

    pthread_rwlock_rdlock(&rwlock); //read lock
    string result = "not found";
    my_map::const_iterator iter = m.find(key);
    if ( iter != m.end() )//found
    {
       result = iter->second;
    }
    else //missing
    {
        pthread_rwlock_wrlock(&rwlock); // write lock
        //fetch value from data base


        //if successful, add to the map
          m[key] = "missing data";
          result = "missing data";
     pthread_rwlock_unlock(&rwlock); // unlock write lock
    }
    pthread_rwlock_unlock(&rwlock); // unlock read lock
    return result;
}

这个函数线程安全吗?两个或多个线程是否有可能在写锁上排队并从数据库中查询相同的键?如果是,我该如何避免这种情况?

【问题讨论】:

  • 当您实现任何需要线程同步的算法时,我会参考的第一个资源是The Little Book of Semaphores。它确实是常见(但不那么常见)同步模式的绝佳参考。
  • 感谢大家的cmets。我会与 James 的建议一起去从 map 读取后释放读锁,并在获取写锁后仔细检查元素是否已经存在于 map 中。

标签: c++ multithreading stl pthreads


【解决方案1】:

此函数不是线程安全的,因为它会导致未定义的行为。当您尝试获取写锁时,您已经持有读锁。来自the documentation for pthread_rwlock_wrlock:

如果调用线程在调用 [pthread_rwlock_wrlock] 时持有读写锁(无论是读锁还是写锁),则结果未定义。

此解决方案也不是异常安全的。如果在持有锁的时候抛出异常,锁不会被释放,你的应用程序无疑会死锁。您应该使用 C++ 线程库(Boost.Thread、OpenThreads、just::thread 或类似的东西)提供面向 C++ 的设计,支持 scoped_lock(或 lock_guard)之类的东西。

至于使算法正确,您需要以下内容:

obtain read lock
attempt to find object
if object exists
    return object
else
    release read lock
    obtain write lock
    if object exists
        return object
    else
        insert object
        return object

[如果您使用某种lock_guard,则无需担心返回时释放持有的锁]

【讨论】:

  • 我认为他的意思是按照函数中第一条语句的 cmets 的“读锁”
  • @Chubsdad:我不明白你的意思。 OP 正在使用读写锁。
  • 我知道。由于评论提到“读锁”(因此有一个单独的读锁和写锁),我只是给 OP 怀疑的好处
  • @Chubsdad:有一个名为rwlock 的锁,但您可以将其锁定为读取器或写入器,具体取决于您传递给它的锁定函数。查看注释为“write lock”的行,您会看到它仍然引用相同的rwlock 变量。
【解决方案2】:

未正确实施。你不能在持有读锁的同时持有写锁,因为害怕死锁。从我的 Linux 机器上 pthread_rwlock_wrlock 的手册页:

pthread_rwlock_wrlock() 函数应将写锁应用于 rwlock 引用的读写锁。调用线程获取 如果没有其他线程(读取器或写入器)持有读写锁,则写锁 锁定 rwlock。否则,线程将阻塞,直到它可以获取 锁。 如果在调用时调用线程可能会死锁 使其持有读写锁(无论是读锁还是写锁)。

此外,您应该检查调用的返回值...例如,对同时读取的数量存在实现定义的限制。

还有一些常见的异常安全问题...考虑使用范围保护或 try/catch 块。

【讨论】:

  • "实现定义的同时阅读器数量限制。" - 好点托尼。谢谢。
【解决方案3】:

一旦你获得了写锁,你可能会修复它再次寻找值。这应该足以解决您描述的问题。比如:

string GetData (const int key)
{

    pthread_rwlock_rdlock(&rwlock); //read lock
    string result = "not found";
    my_map::const_iterator iter = m.find(key);
    if ( iter != m.end() )//found
    {
        result = iter->second;
    }
    else //missing
    {
        // change from read mode to write mode
        pthread_rwlock_unlock(&rwlock); // unlock read lock
        pthread_rwlock_wrlock(&rwlock); // write lock

        // Try again
        iter = m.find(key);
        if (iter != m.end()) {
            result = iter->second;
        } else {
            //if successful, add to the map
            m[key] = "missing data";
            result = "missing data";
        }
    }
    pthread_rwlock_unlock(&rwlock); // unlock read/write lock
    return result;
}

【讨论】:

  • 詹姆斯和我记录的死锁问题仍然存在。
  • ¿可以通过在写锁之前释放读锁来修复它吗?我已经更新了我的答案。当然,仍然有 James 暴露的异常安全问题。
  • 谢谢@@jbernadas,这行得通。不确定我是否可以接受两个答案。
  • 你不能,但别担心,我也认为詹姆斯的回答要好得多。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-11-23
  • 1970-01-01
  • 2010-09-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多