4.c++语言级别的多线程编程

4.c++语言级别的多线程编程

通过thread类编写C++多线程程序

线程内容:

1、如何创建启动一个线程?

​ std::thread定义一个线程对象,传入线程所需的线程函数和参数,线程自动开启

2、子线程如何结束?

​ 子线程函数运行完成,线程就结束了

3、主线程如何处理子线程

​ t.join() : 等待t线程结束,当前线程继续往下运行

​ t.detach() : 把t线程设置为分离线程,主线程结束,整个进程结束,所有子线程都自动结束

#include <thread>

void threadHandler1(int time){
    std::this_thread::sleep_for(std::chrono::seconds(time));
    std::cout<<"call thread1"<<std::endl;
}

void threadHandler2(int time){
    std::this_thread::sleep_for(std::chrono::seconds(time));
    std::cout<<"call thread2"<<std::endl;
}

int main(){
    std::thread t1(threadHandler1,2);
    std::thread t2(threadHandler2,3);
    /*t1.join();
    t2.join();*/
    t1.detach();
    t2.join();
    std::cout<<"main thread done!"<<std::endl;
    return 0;
}
/*
打印输出:
call thread1
call thread2
main thread done!
*/

线程间的互斥锁mutex和lock_guard

上锁常用lock_guard,他是对mutex的封装,出作用域后会自动析构,析构的时候会释放锁,构造函数会上锁

普通的互斥锁是:

std::mutex mtx;
mtx.lock();
//临界区
mtx.unlock();

使用lock_guard是这样:

{
	std::lock_guard<std::mutex> lockGuard(mtx);//出作用域后会析构解锁,默认构造是加锁
	//临界区
}

互斥锁的应用:

//模拟买票
int ticketCount=100000;
std::mutex mtx;

void sellTicket(int index){
    while(ticketCount>0){//可能会发生提前进入循环的情况,所以需要在循环内再加一层判断
//        mtx.lock();
        {//
            std::lock_guard<std::mutex> lockGuard(mtx);//出作用域后会析构解锁,默认构造是加锁
            if(ticketCount>0){
                //下面两句属于临界区代码,该代码段中的操作必须是原子操作,要保证线程之间要互斥。
                std::cout<<"窗口"<<index<<"卖出第"<<ticketCount<<"张票"<<std::endl;
                ticketCount--;
            }
        }
//        mtx.unlock();
    }
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
}

int main(){
    std::list<std::thread> threadList;

    for(int i=1;i<=3;++i){
        threadList.push_back(std::thread(sellTicket,i));
    }

    for(auto &th:threadList){
        th.join();
    }

    std::cout<<"main thread done!"<<std::endl;
    return 0;
}

线程间的同步通讯机制

生产者,消费者线程模型

C++ STl中的所有容器都不是线程安全的

使用条件变量condition_variable做线程间的通信操作

线程间同步通信最典型的例子就是生产者-消费者模型,生产者线程生产出产品以后,会通知消费者线程去消费产品;如果消费者线程去消费产品,发现还没有产品生产出来,它需要通知生产者线程赶快生产产品,等生产者线程生产出产品以后,消费者线程才能继续往下执行。

C++11 线程库提供的条件变量condition_variable,就是Linux平台下的Condition Variable机制,用于解决线程间的同步通信问题,下面通过代码演示一个生产者-消费者线程模型,仔细分析代码:

// 定义互斥锁(条件变量需要和互斥锁一起使用)
std::mutex mtx;
// 定义条件变量(用来做线程间的同步通信)
std::condition_variable cv;
// 定义vector容器,作为生产者和消费者共享的容器
std::vector<int> vec;

// 生产者线程函数
void producer()
{
    // 生产者每生产一个,就通知消费者消费一个
    for (int i = 1; i <= 10; ++i)
    {
        // 获取mtx互斥锁资源
        std::unique_lock<std::mutex> lock(mtx);

        // 如果容器不为空,代表还有产品未消费,等待消费者线程消费完,再生产
        while (!vec.empty())
        {
            // 判断容器不为空,进入等待条件变量的状态,释放mtx锁,
            // 让消费者线程抢到锁能够去消费产品
            cv.wait(lock);
        }
        vec.push_back(i); // 表示生产者生产的产品序号i
        std::cout << "producer生产产品:" << i << std::endl;

        /*
         * 容器为空,说明需要生产,生产完对其他线程进行通讯
        生产者线程生产完产品,通知等待在cv条件变量上的消费者线程,
        可以开始消费产品了,然后释放锁mtx
        */
        cv.notify_all();

        // 生产一个产品,睡眠100ms
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}
// 消费者线程函数
void consumer()
{
    // 消费者每消费一个,就通知生产者生产一个
    for (int i = 1; i <= 10; ++i)
    {
        // 获取mtx互斥锁资源
        std::unique_lock<std::mutex> lock(mtx);

        // 如果容器为空,代表还有没有产品可消费,等待生产者生产,再消费
        while (vec.empty())
        {
            // 判断容器为空,进入等待条件变量的状态,释放mtx锁,
            // 让生产者线程抢到锁能够去生产产品
            cv.wait(lock);//在等待过程中如果收到信息就会进入阻塞状态,执行下一步操作
        }
        int data = vec.back(); // 表示消费者消费的产品序号i
        vec.pop_back();
        std::cout << "consumer消费产品:" << data << std::endl;

        /*
        消费者消费完产品,通知等待在cv条件变量上的生产者线程,
        可以开始生产产品了,然后释放锁mtx
        */
        cv.notify_all();

        // 消费一个产品,睡眠100ms
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}
int main()
{
    // 创建生产者和消费者线程
    std::thread t1(producer);
    std::thread t2(consumer);

    // main主线程等待所有子线程执行完
    t1.join();
    t2.join();

    return 0;
}

再谈lock_guard和unique_lock

互斥锁只有一个线程可以拿到,其他线程如果试图拿到被别的线程占用的互斥锁的时候会进入阻塞状态

std::lock_guard<std::mutex> lockGuard(mtx);不能用在函数参数传递或者返回过程中,只能用在简单的临界区代码段的互斥操作中

unique_lock可以使用在简单的临界区代码段的互斥操作中,也可以用在函数的调用过程中:

unque_lock<std::mutex> lck(mtx);
cv.wait(lck);//cv是condition_variable wait方法会先使线程进入等待状态,然后调用lck.unlock()方法把mtx释放掉。

cv.notify_all()是通知在cv上等待的线程条件成立了该干活了,收到通知的线程会先从等待状态切换到阻塞状态,如果获取到互斥锁那么该线程就会继续执行。

基于CAS操作的atomic原子类型

首先要包含atomic类库

一般在++等简单操作中使用无锁操作(CAS)来实现

//原子整型,CAS操作保证给count自增自减的原子操作
std::atomic_int mycount(0);

//线程函数
void sumTask()
{
    //每个线程给count加1000次
    for (int i = 0; i < 1000; ++i)
    {
        mycount++;
    }
}

int main()
{
    //创建10个线程放在容器当中
    std::vector<std::thread> vec;
    for (int i = 0; i < 10; ++i)
    {
        vec.push_back(std::thread(sumTask));
    }

    //等待线程执行完成
    for (unsigned int i = 0; i < vec.size(); ++i)
    {
        vec[i].join();
    }

    //所有子线程运行结束,count的结果每次运行应该都是10000
    std::cout << "count : " << mycount << std::endl;

    return 0;
}
hmoban主题是根据ripro二开的主题,极致后台体验,无插件,集成会员系统
自学咖网 » 4.c++语言级别的多线程编程