只使用互斥锁实现读写锁?

5

我试图仅使用互斥锁实现读写锁(只是为了学习),在程序可以处理各种情况的基础上,当我认为已经覆盖了所有角落时,我意识到一点重要的事实:(它在ubuntu中工作)互斥锁应由线程所有者释放。下面是我的实现:

class rw_lock_t{

    int NoOfReaders;
    int NoOfWriters, NoOfWritersWaiting;
    pthread_mutex_t class_mutex;
    pthread_cond_t class_cond;
    pthread_mutex_t data_mutex;

public:

    rw_lock_t()
    : NoOfReaders(0),
      NoOfWriters(0), NoOfWritersWaiting(0)
    {
            pthread_mutex_init(&class_mutex, NULL);
            pthread_mutex_init(&data_mutex, NULL);
            pthread_cond_init(&class_cond, NULL);
    }
    void r_lock()
    {
            pthread_mutex_lock(&class_mutex);
            //while(NoOfWriters!=0 || NoOfWritersWaiting!=0) //Writer Preference
            while(NoOfWriters!=0)
            {
                    pthread_cond_wait(&class_cond, &class_mutex);
            }
            if(NoOfReaders==0)
            {
                    pthread_mutex_unlock(&class_mutex);
                    pthread_mutex_lock(&data_mutex);
                    pthread_mutex_lock(&class_mutex);
                    NoOfReaders++;
                    pthread_mutex_unlock(&class_mutex);
            }
            else if(NoOfReaders>0) //Already Locked
            {
                    NoOfReaders++;
                    pthread_mutex_unlock(&class_mutex);
            }
    }
    void w_lock()
    {
            pthread_mutex_lock(&class_mutex);
            NoOfWritersWaiting++;
            while(NoOfReaders!=0 && NoOfWriters!=0)
            {
                    pthread_cond_wait(&class_cond, &class_mutex);
            }
            pthread_mutex_unlock(&class_mutex);

            pthread_mutex_lock(&data_mutex);
            pthread_mutex_lock(&class_mutex);
            NoOfWritersWaiting--; NoOfWriters++;
            pthread_mutex_unlock(&class_mutex);
    }
    void r_unlock()
    {
            pthread_mutex_lock(&class_mutex);
            NoOfReaders--;
            if(NoOfReaders==0)
                    pthread_mutex_unlock(&data_mutex);
            pthread_mutex_unlock(&class_mutex);
            pthread_cond_signal(&class_cond);
    }
    void w_unlock()
    {
            pthread_mutex_lock(&class_mutex);
            NoOfWriters--;
            if(NoOfWriters==0)
                    pthread_mutex_unlock(&data_mutex);
            pthread_mutex_unlock(&class_mutex);
            pthread_cond_signal(&class_cond);
    }
};

我的问题是,最小的更改方式是什么。Semaphore 绝对是最佳选择,但我想到以下解决方案:
解决方案#1
1) 我将有一个专用线程,只用于读取情况下锁定/解锁互斥量。
2) 该线程将在条件变量上等待来自 r_lock 或 r_unlock 的信号。
3) r_lock 和 r_unlock 将不再执行 "pthread_mutex_lock/unlock(&data_mutex);",而是发出信号让专用线程进行锁定。
4) 对于此实现,我必须记住许多事实,
- 信号和实际锁定是两个不同的事件,因此可能需要同步。 - 需要额外的互斥量+条件变量+线程和更多同步。
更新:解决方案#2
1) 执行实际锁定的线程将全局保留其 tid。
2) 每当一个线程解锁时,将确保与全局 tid 进行比较。
3) 如果匹配,将等待 "NoOfReaders==0" 条件并解锁它。
那么,是否有更好的方法可以纠正程序呢?
2个回答

9

如果内部逻辑正确,则不需要为数据单独使用互斥锁。整个结构将作为数据锁定。相反,您可以使用两个单独的条件变量,用于读者和写者,这样您就可以广播所有等待读者而不影响等待写者。下面是代码;您还可以看到这种方式更简单。此外,我添加了一个析构函数,并在w_lock中修复了一个错误:等待的条件应该是(NoOfReaders!= 0 || NoOfWriters!= 0),而不是&&

class rw_lock_t {

    int NoOfReaders;
    int NoOfWriters, NoOfWritersWaiting;
    pthread_mutex_t class_mutex;
    pthread_cond_t  reader_gate;
    pthread_cond_t  writer_gate;

public:

    rw_lock_t()
    : NoOfReaders(0), NoOfWriters(0), NoOfWritersWating(0),
      class_mutex(PTHREAD_MUTEX_INITIALIZER),
      reader_gate(PTHREAD_COND_INITIALIZER),
      writer_gate(PTHREAD_COND_INITIALIZER)
    {}
    ~rw_lock_t()
    {
        pthread_mutex_destroy(&class_mutex);
        pthread_cond_destroy(&reader_gate);
        pthread_cond_destroy(&writer_gate);
    }
    void r_lock()
    {
        pthread_mutex_lock(&class_mutex);
        //while(NoOfWriters>0 || NoOfWritersWaiting>0) //Writer Preference
        while(NoOfWriters>0)
        {
            pthread_cond_wait(&reader_gate, &class_mutex);
        }
        NoOfReaders++;        
        pthread_mutex_unlock(&class_mutex);
    }
    void w_lock()
    {
        pthread_mutex_lock(&class_mutex);
        NoOfWritersWaiting++;
        while(NoOfReaders>0 || NoOfWriters>0)
        {
            pthread_cond_wait(&writer_gate, &class_mutex);
        }
        NoOfWritersWaiting--; NoOfWriters++;
        pthread_mutex_unlock(&class_mutex);
    }
    void r_unlock()
    {
        pthread_mutex_lock(&class_mutex);
        NoOfReaders--;
        if(NoOfReaders==0 && NoOfWritersWaiting>0)
            pthread_cond_signal(&writer_gate);
        pthread_mutex_unlock(&class_mutex);
    }
    void w_unlock()
    {
        pthread_mutex_lock(&class_mutex);
        NoOfWriters--;
        if(NoOfWritersWaiting>0)
            pthread_cond_signal(&writer_gate);
        //else //Writer Preference - don't signal readers unless no writers
        pthread_cond_broadcast(&reader_gate);
        pthread_mutex_unlock(&class_mutex);
    }
};

3
使用条件,并不仅限于互斥锁。 - Alexandru
这是一个完全可以接受的答案。请不要作业! - Kobor42
在r_lock()和w_lock()函数中,我们不应该检查虚假唤醒条件吗?这是来自manpage的内容:“使用条件变量时,每个与条件等待相关联的共享变量都有一个布尔谓词,如果线程应该继续,则为真。 pthread_cond_timedwait()或pthread_cond_wait()函数可能会发生虚假唤醒。由于从pthread_cond_timedwait()或pthread_cond_wait()返回并不意味着关于此谓词值的任何内容,因此应在此类返回时重新评估谓词。”--->如果为假,请返回循环! - cforfun
@cforfun,代码已经完成了,因为pthread_cond_wait()在循环中使用以检查条件。 - Alexey Kukanov
1
@CppNoob,在这段代码中,广播是无条件的,并且可以在解锁互斥锁后执行。然而,两个信号都需要满足一定的条件。这些条件检查rwlock状态,该状态受到互斥锁的保护,以防止并发访问。如果您将这些检查移出互斥锁保护,就会在代码中创建竞态条件,导致未定义的行为。 - Alexey Kukanov
显示剩余4条评论

-1
class ReadWriteLock {
    mutex writeLock;
    mutex readLock;
    int readCount;
public:
    ReadWriteLock() {
        readCount = 0;
    }
    void LockWrite() {
        writeLock.lock();
    }
    void UnlockWrite() {
        writeLock.unlock();
    }
    void LockRead() {
        lock_guard<mutex> lock(readLock);
        ++readCount;
        if (1 == readCount) {
            LockWrite();
        }
    }
    void UnlockRead() {
        lock_guard<mutex> lock(readLock);
        --readCount;
        if (0 == readCount) {
            UnlockWrite();
        }
    }
};

正如Alexey所指出的那样,如果最后一个读取线程到UnlockWrite不是第一个读取线程到LockWrite,则行为是未定义的。请参见std::mutex::unlock http://www.cplusplus.com/reference/mutex/mutex/unlock/ Windows ReleaseMutex: http://msdn.microsoft.com/en-us/library/windows/desktop/ms685066(v=vs.85).aspx

存在一个问题,即最后一个解锁写入互斥量的读取器线程可能与第一个锁定写入互斥量的读取器线程不同。对于 std::mutex,这是不允许的 - 调用 unlock() 的线程应该拥有互斥量。 - Alexey Kukanov
Alexey,你是对的。所以我得到的解决方案就像你的一样,使用了cv。 - QAMichaelPeng
互斥锁解锁的POSIX实现: pthread_mutex_unlock:http://www.lehman.cuny.edu/cgi-bin/man-cgi?pthread_mutex_lock+3 - QAMichaelPeng

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接