copy on write手法以及对多线程新的理解

11 篇文章 3 订阅
6 篇文章 1 订阅

类对象跨线程处理的是十分棘手的,要想清楚一切可能发生死锁的情况,本篇博客中的例子都是来自muduo网络库陈硕的这本书,这本书前两章读了很多遍才彻底读懂,书中的例子十分具有特点,我十分喜欢,故拿书中举的例子记录在博客之中



加锁的顺序一致

加锁的顺序一定要一致,不要出现线程加锁顺序不一致的情况,因为会有死锁的可能发生。A和B都继承Base,加锁的顺序不一样,可能导致死锁现象的产生。解决办法是通过比较锁地址大小来进行加锁,就是加个ifelse,始终保持先加大的还是先加小的。
在这里插入图片描述


伪共享

伪共享:多根线程在一个缓存行操作,一个CPU的缓存的缓存行还要和其他的CPU缓存的缓存行交互,这就是一种伪共享参考的链接

熟练使用gdb bt把每个线程的栈都会打出来的,如果发生了死锁也可以轻而易举的看出来。

以前我不懂为什么C++容易产生内存泄露,深入学习多线程之后,尤其一个类的对象是跨线程的,对象什么时候要delete分析出来是十分复杂且麻烦的事情。

#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
#include <unistd.h>
#include <deque>
#include <atomic>

using namespace std;

using VecI = deque<int>;
shared_ptr<VecI> g_ptr = make_shared<VecI>();// 初始化一下
mutex g_mutex;

void read(){
	unique_lock<mutex> l(g_mutex);
    for(int i = 0;i < g_ptr->size();++i){
   		//doSomething;	
     }
}

void write(int val){
	unique_lock<mutex> l(g_mutex);
	g_ptr->emplace_back(val);
}

在上述代码中,看样子是正确的,我在没看muduo网络库这本书之前,也觉得这么写挺对的,但实际呢,当doSomething间接的调用了write,那么就产生了死锁。可以写一个不加锁的write版本,但又存在新的问题,误用不加锁版本的write怎么办?介绍一个牛逼的做法,copy on write

copy on write

copy on write坦白讲可以理解成多个线程一起读啦,一起读肯定不会有什么线程安全问题的啦。但是要修改某个变量的时候,就需要分道扬镳了,各自维护一个不同缓冲区。或者最后在合并。COW(copy on write的简称)好处如果只是读的话没必要大费周章的去拷贝,拷贝会耗时,一起读就好了。当需要改变这个副本的时候再进行深拷贝即可。举个例子,像fork函数的子进程拥有父进程的副本,也是使用了COW技术,当子进程修改副本的时候再进行拷贝。他们的文件表是共享的,也就是当前文件偏移量都可以知道了,父进程写完子进程写是不会有问题的。参考APUE184页,附上自己写的小demo

#include <sys/types.h>
#include <unistd.h>
#include<iostream>
#include<sys/wait.h>
#include <signal.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
using namespace std;
int main(){

    int fd = ::open("./hello.txt",O_RDWR);
    if(fd < 0){
        cout << "打开失败" << endl;
        return -1;
    }
    if(fork() == 0){
        char* buff = "I am son process\n";
        ::write(fd,buff,strlen(buff));
        exit(0);
    }
    //sleep(2); // 加上让子进程先写。
    char* buff = "I am father process\n";
    ::write(fd,buff,strlen(buff));

    return 0;
}

read中稍微改动一下,也减少了临界区,后来加入的再晚点调用。

void read(){
    shared_ptr<VecI> tmp;
    {
        unique_lock<mutex> l(g_mutex);
        tmp = g_ptr;
    }
     for(int i = 0;i < tmp->size();++i){
		//doSomething
    }

write函数中也需要改一下,如果没有再读,就直接插入数据,因为这是一个共享的智能指针,如果有再读的线程,那就重新new一下,old地址还没有delete,等read线程读完了,old地址智能指针就会释放掉了。copy on write设计是真的很精妙,书上给出了3种错误的copy on write的写法,书中留作的是思考题,再博客中记录下自己所思考的东西,想了差不多一个下午的时间。

void write(int val){
    unique_lock<mutex> l(g_mutex);
    if(!g_ptr.unique()){
        g_ptr.reset(new VecI(*g_ptr));
    }
    g_ptr->emplace_back(val);
}

以下的代码都是错误的!!

// 错误1 
/*
*   错误原因:同时操作了一个shared_ptr
*   但经过测试,数据没少,但是同时操作操作了shared_ptr
*/
void write(){
    ++num;
    unique_lock<mutex> l(g_mutex);
    g_ptr->emplace_back(2);
}

// 错误2,会出现core dump,原因如下
/*
*  假设有两根子线程,线程1在加锁的时候,重新给g_ptr赋值,
*  老的g_ptr被释放掉了,但是,此时很不幸,线程2拿到的是老的g_ptr的值,
*  线程2用*nullptr就出现段错误了(core dump)
*/
void write(){
    ++num;
    shared_ptr<VecI> newPtr = make_shared<VecI>(*g_ptr);
    newPtr->emplace_back(2);
    unique_lock<mutex> l(g_mutex);  
    g_ptr = newPtr;
}

// 错误三
/*
*   数据有损失了但不会core dump
*	同时存在多个newPtr,以最后赋值的newPtr赋值,当然会出现数据丢失现象
*/
void write(){
    ++num;
    shared_ptr<VecI> oldPtr;
    {
        unique_lock<mutex> l(g_mutex);
        oldPtr = g_ptr;
    }
    shared_ptr<VecI> newPtr = make_shared<VecI>(*oldPtr);
    newPtr->emplace_back(2);
    unique_lock<mutex> l(g_mutex);
    g_ptr = newPtr;
}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值