C++多线程编程2_互斥量,死锁

互斥量:

多线程竞争:

上图就是多线程竞争的案例,两个线程t1t2同时看到了arr,然后两个人都想把arr扩容,结果就引发了double free。所以,两个线程应该是要么t1在push,要么t2在push。

所以这时候就要用std::mutex 

#include <iostream>
#include <string>
#include <thread>
#include <vector>
#include <mutex>

int main() {
    std::vector<int> arr;
    std::mutex mtx;
    std::thread t1([&] {
        for (int i = 0; i < 1000; i++) {
            mtx.lock();//上锁
            arr.push_back(1);
            mtx.unlock();//解锁
        }
    });
    std::thread t2([&] {
        for (int i = 0; i < 1000; i++) {
            mtx.lock();
            arr.push_back(2);
            mtx.unlock();
        }
    });
    t1.join();
    t2.join();
    return 0;
}

这就像是我们去厕所一样,在上厕所的时候就把门锁上,之后再把锁打开,其他人才可以进来。

但是这就又回到我们RAII思想了,如果在一次忘记unlock了,那么就会出现线程t2一直等待t1的现象,所以,我们应该用一个类来完成这个操作。

std::lock_guard

#include <iostream>
#include <string>
#include <thread>
#include <vector>
#include <mutex>

int main() {
    std::vector<int> arr;
    std::mutex mtx;
    std::thread t1([&] {
    for(int i = 0 ; i<1000 ; i++){
        std::lock_guard grd(mtx); //离开所在花括号自动析构(unlock)   
        arr.push_back(1);
        }
    });
    
    std::thread t2([&] {
    for(int i = 0 ; i<1000 ; i++){
        std::lock_guard grd(mtx);        
        arr.push_back(2);
        }
    });


    t1.join();
    t2.join();
    return 0;
}

但是lock_guard 太过于死板,而且还不能提前解锁,所以提出了std::unique_lock 这个也符合RAII思想,但是自由度更高。

#include <iostream>
#include <string>
#include <thread>
#include <vector>
#include <mutex>

int main() {
    std::vector<int> arr;
    std::mutex mtx;
    std::thread t1([&] {
        for (int i = 0; i < 1000; i++) {
            std::unique_lock grd(mtx);
            arr.push_back(1);
        }
    });
    std::thread t2([&] {
        for (int i = 0; i < 1000; i++) {
            std::unique_lock grd(mtx, std::defer_lock);
            printf("before the lock\n");
            grd.lock();
            arr.push_back(2);
            grd.unlock();//可以提前解锁,这样就将printf放在了lock之外。
            printf("outside of lock\n");
        }
    });
    t1.join();
    t2.join();
    return 0;
}

这个unique_lock也符合RAII思想,并且自由度更高

#include <iostream>
#include <string>
#include <thread>
#include <vector>
#include <mutex>

int main() {
    std::vector<int> arr1;
    std::mutex mtx1;

    std::vector<int> arr2;
    std::mutex mtx2;

    std::thread t1([&] {
        for (int i = 0; i < 1000; i++) {
            {
                std::lock_guard grd(mtx1);
                arr1.push_back(1);
            }

            {
                std::lock_guard grd(mtx2);
                arr2.push_back(1);
            }
        }
    });
    std::thread t2([&] {
        for (int i = 0; i < 1000; i++) {
            {
                std::lock_guard grd(mtx1);
                arr1.push_back(2);
            }

            {
                std::lock_guard grd(mtx2);
                arr2.push_back(2);
            }
        }
    });
    t1.join();
    t2.join();
    return 0;
}

当然了,如果有多个对象需要上锁,也可以使用多个mutex。然后用{}限制作用域。

try_lock()

 如果你只是想看看有没有锁,没锁就进去,锁了就走,也不等待,那么可以用try_lock(),这个就是看一眼你有没有锁,返回一个true或者false。然后要是没有锁的话就会进去之后自动上锁。

还有就是等待一段时间try_lock_for() 这里定义要用std::timed_mutex mtx1;

这个会等待一段时间,如果超过了这段时间就会返回false。

#include <cstdio>
#include <mutex>

std::timed_mutex mtx1;

int main() {
    if (mtx1.try_lock_for(std::chrono::milliseconds(500)))
        printf("succeed\n");
    else
        printf("failed\n");

    if (mtx1.try_lock_for(std::chrono::milliseconds(500)))
        printf("succeed\n");
    else
        printf("failed\n");

    mtx1.unlock();
    return 0;
}

同理还有try_lock_until()

std::unique_lock:用 std::try_to_lock 做参数

#include <cstdio>
#include <thread>
#include <mutex>

int main() {
    std::mutex mtx;
    std::thread t1([&] {
        std::unique_lock grd(mtx, std::try_to_lock);
        if (grd.owns_lock())
            printf("t1 success\n");
        else
            printf("t1 failed\n");
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    });

    std::thread t2([&] {
        std::unique_lock grd(mtx, std::try_to_lock);
        if (grd.owns_lock())
            printf("t2 success\n");
        else
            printf("t2 failed\n");
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    });

    t1.join();
    t2.join();
    return 0;
}

在这里unique_lock不会上锁,而是会去执行try_lock()。之后owns_lock返回自身状态,也可以检查是否上锁。

死锁

比如多个mutex:

#include <iostream>
#include <string>
#include <thread>
#include <mutex>

int main() {
    std::mutex mtx1;
    std::mutex mtx2;

    std::thread t1([&] {
        for (int i = 0; i < 1000; i++) {
            mtx1.lock();
            mtx2.lock();
            mtx2.unlock();
            mtx1.unlock();
        }
    });
    std::thread t2([&] {
        for (int i = 0; i < 1000; i++) {
            mtx2.lock();
            mtx1.lock();
            mtx1.unlock();
            mtx2.unlock();
        }
    });
    t1.join();
    t2.join();
    return 0;
}

在这里可能出现这种情况:

t1 mtx1.lock()

t2 mtx2.lock()

t1 mtx2.lock()//等待

t2 mtx1.lock()//等待

这样就造成了死锁问题。

解决方法1 永远不要同时持有两个锁。在lock执行完后立即unlock。

解决方法2 双方上锁顺序一致。比如

std::thread t1([&] {
        for (int i = 0; i < 1000; i++) {
            mtx1.lock();
            mtx2.lock();
            mtx2.unlock();
            mtx1.unlock();
        }
    });
    std::thread t2([&] {
        for (int i = 0; i < 1000; i++) {
            mtx1.lock();
            mtx2.lock();
            mtx2.unlock();
            mtx1.unlock();
        }
    });

这样就是

t1 mtx1.lock()
t2 mtx1.lock()//等待 
t1 mtx2.lock()
t2 mtx1.lock()//等待 
t1 mtx1.unlock()
t2 mtx1.lock()//执行 

解决方法3 std::lock()同时对多个锁上锁

int main() {
    std::mutex mtx1;
    std::mutex mtx2;

    std::thread t1([&] {
        for (int i = 0; i < 1000; i++) {
            std::lock(mtx1, mtx2);
            mtx1.unlock();
            mtx2.unlock();
        }
    });
    std::thread t2([&] {
        for (int i = 0; i < 1000; i++) {
            std::lock(mtx2, mtx1);
            mtx2.unlock();
            mtx1.unlock();
        }
    });
    t1.join();
    t2.join();
    return 0;
}

这样就可以保证无论线程中调用的顺序是否相同,都不会出现死锁的问题了。

同时std::lock的RAII版本:std::scoped_lock()//c++17

他和lock_guard相对应,不同的是他可以对多个mutex上锁

std::thread t1([&] {
        for (int i = 0; i < 1000; i++) {
            std::scoped_lock grd(mtx1, mtx2);
            // do something
        }
    });
    std::thread t2([&] {
        for (int i = 0; i < 1000; i++) {
            std::scoped_lock grd(mtx2, mtx1);
            // do something
        }
    });

同一个线程重复调用lock也会死锁:

std::mutex mtx1;

void other() {
    mtx1.lock();
    // do something
    mtx1.unlock();
}

void func() {
    mtx1.lock();
    other();
    mtx1.unlock();
}

int main() {
    func();
    return 0;
}

这里就是,当func调用了lock之后执行了other语句,而other语句又将mtx1.lock调用了,但这个时候由于mtx1已经被锁住,所以只能等mtx1释放后再调用,但是mtx1得在other之后才能调用,所以造成了死锁。

解决方法:std::recursive_mutex 他会判断是不是同一个线程lock了多次,里面有计数器

#include <iostream>
#include <mutex>

std::recursive_mutex mtx1;

void other() {
    mtx1.lock();
    // do something
    mtx1.unlock();
}

void func() {
    mtx1.lock();
    other();
    mtx1.unlock();
}

int main() {
    func();
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值