在之前的博客中我們說明了生產者消費者模型,在這篇博客中我們討論類似的模型讀者寫者模型。
概念
讀者寫者模型是操作系統的一種同步與互斥機制,與生產者消費者模型相似,區別在於多個讀者可以共享緩衝區,但是對於寫者而言相互是競爭關係,一次只能有一個寫者,寫入緩衝區。
讀者寫者模型具有的條件
- 寫者排他性:有多個寫者的情況下,只有一個寫者能夠佔用緩衝區。
- 讀者的並行機制:可以有多個讀者同時使用緩衝區。
- 如果寫者佔用緩衝區,則讀者會被阻塞。
讀者寫者模型關係
-
讀者優先:讀者先進行讀數據,寫者暫時阻塞,直到緩衝區中無數據,寫者訪問倉庫。
-
寫者優先:寫者先進行寫數據,讀者暫時阻塞,直到緩衝區中滿數據,讀者訪問倉庫。
-
公平情況:讀者寫者優先級相同,誰先進優先級隊列,誰先運行。
讀寫鎖
我們使用讀寫鎖實現讀者寫者模型,讀寫鎖和mutex相似,一共有三種狀態,讀模式下加鎖,寫模式下加鎖,解鎖。
在同一時間中,只能有一個線程申請寫鎖,但是有多個線程申請讀鎖。
讀寫鎖狀態
- 寫加鎖狀態:當處於寫加鎖狀態,所有申請鎖的進程都會阻塞。
- 讀加鎖狀態:處於讀加鎖狀態,申請讀模式下的鎖的進程都能獲得訪問,但是對於申請寫模式下的鎖的進程受到阻塞。
當讀寫鎖處於讀模式下,有進程申請寫模式的讀寫鎖會導致,之後的申請讀模式的讀寫鎖的進程遭到阻塞,原因是防止讀模式長期佔用資源,導致寫進程飢餓。
遞歸鎖/非遞歸鎖
遞歸鎖也叫可重入鎖(reentrant mutex),非遞歸鎖也叫不可重入鎖(non-reentrant mutex)。
二者唯一的區別是:
同一個線程可以多次獲取同一個遞歸鎖,不會產生死鎖。
如果一個線程多次獲取同一個非遞歸鎖,則會產生死鎖。
讀寫鎖接口
設置讀寫優先
int pthread_rwlockattr_setkind_np(pthread_rwlockattr_t *attr,int pref);
/*
pref 共有三種參數
PTHREAD_RWLOCK_PREFER_READER_NP 讀者優先,可能會導致寫者飢餓。
PTHREAD_RWLOCK_PREFER_WRITER_NP 寫者優先
PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP 寫者優先,但是不能遞歸加鎖
*/
初始化
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr);
銷燬
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
加鎖和解鎖
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
相關案例:
//
#include <iostream>
#include <vector>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>
using namespace std;
volatile int ticket = 1000;
pthread_rwlock_t rwlock;
class ThreadAttr
{
pthread_t tid;
string id;
};
void* reader(void* arg)
{
char* id = (char*)arg;
while (1)
{
pthread_rwlock_rdlock(&rwlock);
if (tiket <= 0)
{
pthread_rwlock_unlock(&rwlock);
break;
}
printf("%s:%d\n", id, ticket);
pthread_rwlock_unlock(&rwlock);
sleep(1);
}
return nullptr;
}
void* writer(void* arg)
{
char* id = (char*)arg;
while (1)
{
pthread_rwlock_rdlock(&rwlock);
if (tiket <= 0)
{
pthread_rwlock_unlock(&rwlock);
break;
}
printf("%s:%d\n", id, --ticket);
pthread_rwlock_unlock(&rwlock);
sleep(1);
}
return nullptr;
}
string create_reader_id(std::size_t i)
{
ostringstream oss("thread reader", ios_base::ate);
oss << i;
return oss.str();
}
string create_writer_id(std::size_t i)
{
ostringstream oss("thread writer", ios_base::ate);
oss << i;
return oss.str();
}
void init_readers(vector<ThreadAttr>& vec)
{
for (size_t i = 0; i < vec.size(); i++)
{
vec[i].id = create_read_id();
pthread_create(&vec[i].tid, nullptr, read, (void*)vec[i].id.c_str());
}
}
void init_writers(vector<ThreadAttr>& vec)
{
for (size_t i = 0; i < vec.size(); i++)
{
vec[i].id = create_writer_id();
pthread_create(&vec[i].tid, nullptr, writer, (void*)vec[i].id.c_str());
}
}
void join_thread(vector<ThreadAttr> const& vec)
{
for (vector<ThreadAttr>::const_reverse_iterator it = vec.rbegin(); it != vec.rend(); it++)
{
pthread const& tid = it->tid;
pthead_join(tid, nullptr);
}
}
void init_rwlock()
{
//寫優先
pthread_rwlockattr_t attr;
pthread_rwlockattr_init(&attr);
pthread_rwlockattr_setkind_np(&attr, PTHREAD_RWLOCK_PREFER_WRITER_NP);//寫者優先
pthread_rwlock_init(&rwlock, &attr);
pthread_rwlockattr_destroy(&attr);
//讀優先 pthread_rwlock_init(&rwlock, nullptr);
}
int main()
{
const size_t reader_num = 1000;
const size_t writer_num = 2;
vector<ThreadAttr> reader(reader_num);
vector<ThreadAttr> writer(writer_num);
init_rwlock();
init_readers(reader);
init_readers(writer);
join_threads(reader);
join_threads(writer);
pthread_rwlock_destroy(&rwlock);
}