C++中多线程的运用

thread库简单介绍

使用thread库时要包括这个头文件: #include <thread>

构造函数

thread()

  • 构造一个线程对象,没有关联任何线程函数,即没有启动任何线程

thread(fn, args1, args2, ...)

  • 构造一个线程对象,关联线程函数fn并传入参数args1、args2.....

其他成员函数

get_id()

  • 获取线程id

  • 常用用法为:获取当前进程id -- this_thread::get_id();

jion()

  • 函数调用后会阻塞线程,当该线程结束后,主线程才会继续执行

joinable()

  • 线程是否还在执行,joinable代表的是一个正在执行中的线程。

detach()

  • 需要在创建后立马调用

  • 用于把该线程和线程对象分离开来,分离出来的线程变成后台进程,死活于主线程无关

sleep_for()

  • 线程休眠某个指定的时间片(time span),该线程才被重新唤醒

  • 是在this_thread命名空间中的辅助函数

yield()

  • 在this_thread命名空间中的辅助函数

  • 使当前线程放弃执行,操作系统会调度另一线程继续执行

线程库的使用

当创建一个线程对象后,我们需要关联线程函数,线程函数的获取方法有三种:

  1. 函数指针

  1. lambda表达式

  1. 函数对象

void funPtr()
{
    cout << "我用于函数指针的使用" << endl;
}

class Fun
{
public:
    void operator()()
    {
        cout << "我用于函数对象的使用" << endl;
    }
};

int main()
{
    //线程函数为函数指针
    thread t1(funPtr);
    
    //线程函数为lambda表达式
    thread t2([](){cout << "我用于lambda表达式" << endl;});

    //线程函数为函数对象
    Fun f;
    thread t3(f);

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

注意:thread是防拷贝的,但是可以进行移动构造和移动赋值(关于移动构造和移动赋值:(4条消息) C++11新特性:右值引用_c++11新特性右值引用_七号鹅毛的博客-CSDN博客

关于线程函数参数:这个线程函数传进去只能作为值引用,想要传引用进去需要像这样:
void Func(int& n)
{//....}

int main()
{
    int n = 10;
    thread t1(Func,ref(n));  //ref来自function,可以强制传引用进去
    return 0;
}

原子性操作库(atomic)

多线程程序中最经常遇到的问题就是线程安全问题

我们可以通过加锁来解决:

//在一个线程函数中:
for (int i = 0; i < n; ++i)
{
    mtx.lock();
    ++x;
    mtx.unlock();
}

上述函数可以解决线程安全问题,但是当n过大的时候,每一个线程在里面加锁又解锁,只做了一个简单的动作,此时加锁解锁产生的消耗会非常多,如果n=1000000,但是又有四个线程,那么这四个线程总共加锁解锁了4000000次,这是一个非常大的消耗。

我们可以使用C++11中引入的原子操作,来保证这个变量在进行++或者--等操作时是原子的:

//atomic<T> x;
atomic<int> x = 0;

for (int i = 0; i < n; ++i)
{
    ++x;
}

lock_guard

这个线程库提供的类模板的原理是RAII,即资源获取就是初始化(Resource Acquisition Is Initialization),RAII利用类对象超出作用域就会调用析构函数的特性,将一些资源的初始化放在构造函数中,资源的删除或取消放在析构函数中,由这个类对象自己管理。

我们可以实现一个简单的lock_guard:

//RAII封装锁
template<class LOCK>
class LockGuard
{
public:
    LockGuard(LOCK& lock) :_lock(lock)
    {
        _lock.lock();
        cout << "加锁" << endl;
    }

    ~LockGuard()
    {
        cout << "解锁" << endl;
        _lock.unlock();
    }

private:
    LOCK& _lock;
};
int main()
{
  {
LockGuard<mutex> lg(mtx);
for (int i = 0; i < n; ++i)
{
    cout << this_thread::get_id() << ":" << i << endl;
    ++x;
}
  }//在这个大括号作用域后,lg会自动解锁
return 0;
};

单例模式中的线程安全

在懒汉单例模式中,多线程访问getInstan时可能无法保证单例模式的安全性,有可能一个线程获得了单例,另一个线程也同时获得了单例。所以我们需要上锁来保证线程安全

//单例模式中的线程安全
class Singleton
{
public:
    static Singleton* getInstan()
    {
        //需要进行双检查
        //保证后续访问时的线程安全和效率
        if (_instan == nullptr)
        {
            LockGuard<mutex> lg(_mtx);
            //保证第一次访问时的线程安全
            if (_instan == nullptr)
            {
                _instan = new Singleton;
            }
        }
        return _instan;
    }

private:
    static Singleton* _instan;
    static mutex _mtx;

private:
    Singleton(){}
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};

Singleton* Singleton::_instan = nullptr;
mutex Singleton::_mtx;

int main()
{
    Singleton::getInstan();
    Singleton::getInstan();

    return 0;
}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值