终此一生,只有两种办法: 要么梦见生活,要么落实生活。 --- 勒内・夏尔 ---
1 读者写者标题
读者写者是一种生产消费模子,所以就满意"321"原则:
- 三种关系:生产与消费,生产与生产,消费与消费
- 两种脚色:生产者与消费者
- 一个交易场合:临界资源
在读者写者标题中,读者与读者是并发的,不同读者之间不会互相影响,因为只是访问数据,并不会读数据举行修改。写者与写者是互斥的,临界资源只能让一个写者举行书写。读者与写者的关系比力复杂,是互斥与同步,读写不能同时举行,读完了要与写举行同步,写完了要与读同步。
一般而言:读者写者模子中读者许多,写者很少。
2 读写锁
读写锁的逻辑可以这么理解:
- 首先必要一个互斥锁,来对写者举行上锁。保证写者与写者之间的互斥关系
- 然后对应读者来说,他们是并发执行的,为了可以保证读完了可以举行写的同步,必要一个计数器来记录读者的数量。
- 有了这个计数器,那么就相称于读者都会访问这个计数器,所以必要锁来举行保护。
- 当进入读者时,先将将计数器锁获取。然后在对计数器举行++,再举行解锁,然后,写锁获取,让写者无法获取锁阻塞 ,举行读操作。之后在将计数器锁获取举行–,再举行解锁
- 当进入写者时,将写者锁获取,之后举行写操作,最后举行解锁。
这是读写锁的逻辑,当现实中线程库为我们提供了专门的读写锁,我们不必要利用互斥锁来举行模仿!
- #include <pthread.h>
- //销毁
- int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
- //初始化
- int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
- const pthread_rwlockattr_t *restrict attr);
- pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
- //读者锁
- 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 <pthread.h>
- #include <iostream>
- #include <vector>
- #include <stdlib.h>
- #include <unistd.h>
- // 进行访问的全局数据
- int Data = 0;
- // 读写锁
- pthread_rwlock_t rwlock;
- // 读操作
- void *read(void *args)
- {
- int id = *(int *)args;
- //sleep(1);
- while (true)
- {
- // 读者上锁
- pthread_rwlock_rdlock(&rwlock);
- // 进行读操作
- std::cout << "读者线程-" << id << "正在读取数据:" << Data << std::endl;
- sleep(1);
- // 完成写操作
- // 解锁
- pthread_rwlock_unlock(&rwlock);
- }
- delete (int*)args;
- return nullptr;
- }
- // 写操作
- void *write(void *args)
- {
- int id = *(int *)args;
- while (true)
- {
- // 读者锁
- pthread_rwlock_wrlock(&rwlock);
- // 写操作
- Data = rand() % 100;
- std::cout << "写者线程-" << id << "正在写入数据:" << Data << std::endl;
- sleep(1);
- // 解锁
- pthread_rwlock_unlock(&rwlock);
- }
- delete (int*)args;
- return nullptr;
- }
- int main()
- {
- // 读者写者数量
- int write_count = 2;
- int read_count = 2;
- std::vector<pthread_t> wthreads(write_count, 0);
- std::vector<pthread_t> rthreads(read_count, 0);
- pthread_rwlock_init(&rwlock, nullptr);
- srand(time(nullptr));
- for (int i = 0; i < read_count; i++)
- {
- int *id = new int(i);
- pthread_create(&rthreads[i], nullptr, read, id);
- }
- for (int i = 0; i < write_count; i++)
- {
- int *id = new int(i);
- pthread_create(&wthreads[i], nullptr, write, id);
- }
- for (int i = 0; i < read_count; i++)
- {
- pthread_join(rthreads[i], nullptr);
- }
- for (int i = 0; i < write_count; i++)
- {
- pthread_join(wthreads[i], nullptr);
- }
- pthread_rwlock_destroy(&rwlock);
- return 0;
- }
复制代码 运行会发现:
写者根本进不来,只有读者在举行,这是因为这里读者读到数据没有举行处理,而是一连的再举行读取,这就导致写者没有机会获取到全局变量,就不能举行写操作。我们可以参加sleep(1)模仿处理数据:如许写者就有机会获取到全局变量举行处理了!!!
3 读写锁的两大特性
在生产者消费者模子中,消费者与生产者的关系是对等的。但在读者写者标题中,读者与写者的关系不对等。一般会有两种计谋:
- 读者优先(Reader-Preference)
在这种计谋中,体系会尽大概多地允许多个读者同时访问资源(比如共享文件或数据),而不会优先考虑写者。这意味着当有读者正在读取时,新到达的读者会立刻被允许进入读取区,而写者则会被阻塞,直到所有读者都离开读取区。读者优先计谋大概会导致写者饥饿(即写者长时间无法获得写入权限),特殊是当读者频仍到达时。
读者优先的现实应用场景:
- 文档数据库:
在文档数据库中,通常读取操作远多于写入操作。采用读者优先计谋可以最大化读取效率,让多个用户同时读取文档而不会相互阻塞。例如,一个在线百科全书网站,用户频仍读取词条内容,但编辑更新的频率相对较低。
- 设置文件读取:
在多线程应用中,设置文件通常会被频仍读取但很少写入。利用读者优先的读写锁可以保证设置文件在更新时不会影响大量读取操作。
- 缓存体系:
缓存体系中的数据读取非常频仍,而写入(缓存失效或更新)相对较少。读者优先计谋可以保证缓存数据的快速访问。
其潜伏标题就是会造成写者饥饿:如果写者操作不频仍,但读者操作非常频仍,写者大概长时间无法获得锁,导致写入操作被无限期耽误。
- 写者优先(Writer-Preference)
在这种计谋中,体系会优先考虑写者。当写者请求写入权限时,体系会尽快地让写者进入写入区,纵然此时有读者正在读取。这通常意味着一旦有写者到达,所有后续的读者都会被阻塞,直到写者完成写入并离开写入区。写者优先计谋可以减少写者等待的时间,但大概会导致读者饥饿(即读者长时间无法获得读取权限),特殊是当写者频仍到达时。
写者优先的现实应用场景:
- 实时数据体系:
在必要实时更新和读取数据的应用中,写者优先计谋可以确保数据的实时性。例如,股票市场信息必要实时更新,并且更新必须尽快反映给所有用户。
- 状态更新:
在某些体系中,状态的更新(写入操作)必要被尽快处理以保证体系的正确性和同等性。例如,游戏状态更新必要及时反映给所有玩家。
- 日志体系:
在日志体系中,写入操作是一连的,且重要性高于读取操作。写者优先可以确保日志记录不会因为读取操作而耽误。
写者优先的潜伏标题是会造成读者饥饿:如果写者操作非常频仍,读者大概会长时间无法获得锁,导致读取操作被阻塞。
总之,读者优先适合读取操作远多于写入操作的场景,可以最大化读取效率。写者优先适合写入操作的重要性高于读取操作的场景,可以确保写入操作的及时性
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |