C++11多线程

程序的描述

  1. 如何创建线程

std::thread mytobj(myPrint);//创建一个线程并开始运行

线程类 线程名 (入口函数)

  • 添加头文件#include <thread>

  • 创建线程入口函数

  • 入口初始函数是一个可以调用的对象,可以是成员函数、函数指针、lambda表达式、bind绑定对象、重载()的类对象(operator())。

  • 线程之间不允许拷贝构造

  1. 回收线程的资源join和detach

join()加入/汇合

obj.join(); //阻止主线程执行,而是等待子线程执行完毕join()才算执行完毕,然后才执行主线程。

  • 就是主线程,让主线程等待子线程执行完毕,然后子线程和主线程汇合,再执行主线程代码。

  • 一个良好的线程程序,应该是主线程等待子线程执行完毕之后,主线程才退出来。

detach()分离

obj.detach(); //主线程与子线程分离开来,主线程自己执行自己的主线程代码,子线程执行自己的子线程代码(交由操作系统处理),主线程不必等待子线程运行结束。

  • 为什么引入detach():假如创建了许多子线程,让主线程逐个等待子线程结束,一般这种情况是不友好的,所以引入了detach()。一旦detach() 之后,与这个主线程关联的thread对象就会失去与主线程之间的关联,此时的子线程就会驻留在后台运行(主线程与子线程失去联系)。

  • 这个子线程就相当于被C++运行时库接管,当这个子线程执行完成后,就会由C++运行时库负责清理该线程相关的资源。(守护进程)

  • detach()使线程入口函数(子线程)失去对于自己的控制。

一般不推荐,会导致很多莫名其妙的bug--来自一位老程序员的心酸告诫

jionable()

  • joinable()判断是否可以成功使用join()或者detach()的,返回true或者false。true表示可以使用join()或者detach(),false表示不可以使用join或者detach()。

  • 一旦调用了join()或者detach(),后续就不能在调用了。

#include <iostream>
#include <thread>
using namespace std;

void myPrint()//成员函数作为入口函数
{
    cout << "成员函数作为线程入口函数" << endl;
    return;
}

//传递参数的时候建议使用引用和指针
void myPrint2(const int& i, char* mybuf)//带参数的成员函数作为入口函数
{
    cout << i << mybuf << "  带参数的成员函数作为入口函数" << endl;
    return;
}

class Ta//仿函数作为入口函数
{
public://解:这里如果用int& i会报错,必须使用const或者不引用,因为创建多线程的本质是复制函数和参数到线程中
    void operator()(int i ,const string&str)
    {
        cout << "仿函数作为入口函数" << endl;
    }
};

class Mythread//静态成员类作为入口函数(本质上和普通函数一样)
{
public://            这里的参数为什么不能用引用?
    static void fun(const int& i,const string buf)
    {
        cout << i << buf << "静态成员函数作为入口函数" << endl;
    }
};

class Mythread2//用类的普通函数作为入口函数
{
public:
    void fun2(const int&i,const char* buf)
    {
        cout << i << buf << "   普通类成员函数作为入口函数" << endl;
    }
};

int main()
{
    int i = 2;
    char buf[] = "aaaa";

    //lammbda函数作为入口函数
    auto f = [](int bh, const string& str)
    {
        cout << bh << str << "   lambda函数作为入口函数" << endl;
    };



    std::thread myobj1(myPrint);//创建了成员函数作为入口的线程
    std::thread myobj2(myPrint2, i, buf);//创建了带参成员函数作为入口函数的线程
    std::thread myobj3(f, 3, "sadfaf");//创建了lambda函数作为入口函数的线程
    std::thread myobj4(Ta(), 4, "仿函数");//创建了仿函数为入口函数的线程
    std::thread myobj5(Mythread::fun,5, "静态函数");//静态函数为入口函数的线程

    //用类的普通函数作为入口函数
    Mythread2 thr;//必须先创建对象、必须保证对象的生命周期比子线程要长。
    std::thread myobj6(&Mythread2::fun2, &thr, 6, "adfa");//第二个参数必须是对象的this指针


    myobj1.join();//等待子线程完成
    myobj2.join();

    if (myobj3.joinable())//判断线程是否可以加join或者detach
    {
        myobj3.join();
    }

    myobj4.join();
    myobj5.join();

    system("PAUSE");
    return 0;
}

输入结果:

成员函数作为线程入口函数2仿函数作为入口函数5静态函数静态成员函数作为入口函数

6adfa   普通类成员函数作为入口函数
aaaa  带参数的成员函数作为入口函数
3sadfaf   lambda函数作为入口函数

请按任意键继续. . .

注:每次结果会不一样

  1. 线程this_thread的全局函数

C++11提供了命名空间this_thread来表示当前线程,该命名空间中有四个函数:get_id()、sleep_for()、

sleep_until()、yield().

this_thread::get_id():获得当前线程的id。

this_thread::sleep_for():休眠函数,VS里使用Sleep(毫秒)作为休眠函数,linux中sleep(秒)作为休眠函数。

因为不同系统的休眠函数不同,为了方便移植C++11提供了sleep_for()函数,让该线程休眠一段时间。

this_thread::sleep_for(chrono::seconds(1))//休眠一秒

sleep_until():让函数休眠至指定时间点(可实现定时任务) 如2023年7月9日 19:04:23

yiled():该函数让线程主动让出自己已经抢到的cpu时间片。

thread类的其他成员函数

void swap(std::thread& other);//交换两个线程对象
如:myobj1.swap(myobj2);//交换线程myobj1和myobj2
static unsigned hardware_concurrency() noexcept;//返回硬件线程上下文的数量
thread t2 = t1;.//报错
thread t2 = move(t1);将t1转为右值,可以,但是代表着t1已经无法使用,将所有权限转移给了t2
  1. call_once函数

在多线程环境中,某些函数只能被调用一次,例如:初始化某个对象,而这个对象只能被初始化一

次。

在线程的任务函数中,可以用std:call_once()来保证某个函数只被调用一次。

需要添加头文件#include<mutex>

std::call_once(once_flag,函数名,参数1,参数2....);//once_flag后面会写是干嘛的

  1. native_handle函数

C++11定义了线程标准,不同的平台和编译器在实现的时候,本质上都是对操作系统的线程库进行

封装,会损失一部分功能。

为了弥补C++11线程库的不足,thread类提供了native_handle()成员函数,用于获得与操作系

统相关的原生线程句柄,操作系统原生的线程库就可以用原生线程句柄操作线程。

6.线程同步/互斥锁mutex

CPU执行指令:读取指令、读取内存、执行指令、写回内存。
i++1)从内存中读取i的值;2)把i+1;3)把结果写回内存。
一个操作(有可能包含有多个步骤)要么全部执行(生效),要么全部都不执行

C++提供了四种互斥锁:

  • mutex:互斥锁(操作系统提供的互斥锁只有这个,其他的是C++11封装的)

  • timed_mutex:带超时机制的互斥锁

  • recursive_mutex:递归互斥锁

  • recursive_timed_mutex:带超时机制的递归互斥锁

包含头文件:#include<mutex>

1.mutex类

  1. 加锁lock();

如果互斥锁是未锁定状态,调用lock()成员函数的线程会得到互斥锁的所有权,并将其上锁。

如果互斥锁是锁定状态,调用lock()成员函数的线程就会阻塞等待,直到互斥锁变成未锁定状态。

  1. 解锁unlock();

只有持有锁的线程才能解锁

  1. 尝试加锁trylock();

如果互斥锁是未锁定状态,则加锁成功,函数返回true。

如果互斥锁是锁定状态,则加锁失败,函数立即返回false。(线程不会阻塞等待)

2.timed_mutex类

timed_mutex//带超时机制的互斥锁

相较于mutex类,timed_mutex增加了两个函数

bool try_lock_for(时间长度)

bool try_lock_until(时间点)

3.rerecursive_mutex

递归互斥锁允许同一线程多次获得互斥锁,可以解决同一线程多次加锁造成的死锁问题。

class Tb
{
mutex m_mutex;//创建一个互斥锁
public:
    void fun1()
    {
        m_mutex.lock();//加锁
        cout << "fun1" << endl;
        m_mutex.unlock();//解锁
    }
    void fun2()
    {
        m_mutex.lock();
        fun1();//还未解锁时又加锁
        cout << "在上锁的时候调用fun1" << endl;
        m_mutex.unlock();
    }
};

此时就出现了死锁,会报错。普通的互斥锁必须在解锁之后才能加锁,上面将mutex类换成rerecursive_mutex类就不会有问题了。

4.lock_guard类

lock_guard是模板类,可以简化互斥锁的使用,也更安全。

lock_guard类在构造中加锁,在析构中解锁。

lock_guard 采用了RAII思想(在类构造函数中分配资源,在析构函数中释放资源,保证资源在离

开作用域时自动释放)。

lock_guard<mutex> mlock(锁对象)//<中可以是上述C++提供的四种互斥锁中的任何一类>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值