C++多线程实现

简介: C++11支持语言级别的多线程编程,可以跨平台运行,支持windows/linux/mac等。

C++11支持语言级别的多线程编程,可以跨平台运行,支持windows/linux/mac等。


主要涉及:


1.thread/mutex/condition_variable

2.lock_quard/unique_lock

3.automic:原子类型,基于CAS操作的原子类型,线程安全的

4.sleep_for


C++的thread本质上还是调用系统支持的函数,windows(createThread)、linux(pthread_create)进行多线程。


初识多线程


1.如何创建启动一个线程?thread来创建一个线程对象,需要线程所需要的线程函数和参数;线程自动开启。

2.子线程如何结束?子线程函数运行完成,线程就结束了。

3.主线程如何处理子线程?join和detach方法。


#include <iostream>
#include <string>
#include<thread>
void threadHandle1() 
{
    // 让子线程睡眠两秒,this_thread获取当前线程,chrono计时的函数
    std::this_thread::sleep_for(std::chrono::seconds(2));
    std::cout << "hello,thread1" << std::endl;
}
void threadHandle2(int time)
{
    // 让子线程睡眠两秒,this_thread获取当前线程,chrono计时的函数
    std::this_thread::sleep_for(std::chrono::seconds(time));
    std::cout << "hello,thread2" << std::endl;
}
int main()
{   
    // 创建了一个线程对象t1,传入一个线程函数,新线程就开始运行了
    std::thread t1(threadHandle1);
    // join是子线程等待主线程结束,主线程继续往下执行,
    // detach则是分离线程,子线程和主线程无关联,可以独立运行,等主线程结束,整个程序结束,所有子线程都自动结束了
    // 传入参数的情况
    std::thread t2(threadHandle2, 2);
    t1.join();
    t2.join();
    std::cout << "main thread hello" << std::endl;
    return 0;
}


mutex和lock


多线程程序中可能会出现,竞态条件:多线程程序执行的结果是一致的,不会随着CPU对线程不同的调用顺序,而产生不同的运行结果。所以需要引入互斥锁来防止多个线程之间的对资源的访问正确性。


初识mutex互斥锁,使用lock和unlock函数完成进程互斥,将会导致程序中断将会导致mutex的内存释放问题。具体如下所示:


// 模拟车站卖票的程序
#include <iostream>
#include <string>
#include<thread>
#include<list>
#include<mutex>
using namespace std;
// 一共有tickCount张票
int tickCount = 10;
// 定义全局互斥锁
mutex mtx;
void sellTicket(int index)
{
    // mtx.lock(); //1、这样就只会存在一个窗口在卖票,因为while循环只支持一个线程访问
    while(tickCount>0)
    {   
        /*
        直接使用mutex.lock()和mutex.unlock()将会出现一个问题,
        当函数还未unlock时就因为程序中间return或者error结束后,
        导致mutex资源无法释放的问题。
        */
        mtx.lock(); //2、仅在临界区代码段 -> 原子操作 -> 线程间互斥操作 -> mutex
        // 在锁里面加判断是防止当一个进程1面临tickCount=1时,还为-1成功,
        // 另一个线程2进入while循环,只是在mtx.lock阻塞了,等进程1-1成功后,
        // 其实进程2获取到的tickCount已经由1->0,但是还是会进行tickCount--
        // 导致了最终卖出-1张票
        if(tickCount>0)
        {
            cout << "窗口:" << index << "卖出第" << tickCount << "张票。" << endl;
            tickCount--;
        }
        mtx.unlock(); //2、
        this_thread::sleep_for(chrono::milliseconds(2));
    }
    // mtx.unlock(); // 1、
}
/*
多线程程序:
竞态条件:多线程程序执行的结果是一致的,不会随着CPU对线程不同的调用顺序,而产生不同的运行结果。
*/
int main()
{
    list<thread> tlist;
    int thread_num = 3;
    for (int i = 0; i < thread_num;i++)
    {
        tlist.push_back(thread(sellTicket, i));
    }
    for (thread &t : tlist)
    {
        t.join();
    }
        return 0;
}


所以进一步引入lock_guard(不可能用在函数参数传递或者返回过程中,也不能赋值使用,只能用于简单的加锁解锁的临界代码段当中)和unique_lock(一般用于进程通信,和condition_varible联合使用)在作用域结束后自动析构,类似于智能指针。如下代码所示


void sellTicket2(int index)
{
    while (tickCount > 0)
    {
        // mtx.lock(); 
        {
          // lock_guard函数删除了拷贝构造函数和操作符=重载,类似于scoped_ptr,但保留了构造函数
            lock_guard<mutex> lock(mtx); //在这个局部作用域中,程序结束后自动析构,如果中间return了也会析构
            if (tickCount > 0)
            {
                cout << "窗口:" << index << "卖出第" << tickCount << "张票。" << endl;
                tickCount--;
        }
        }
        // mtx.unlock(); 
        this_thread::sleep_for(chrono::milliseconds(2));
    }
}
void sellTicket3(int index)
{
    while (tickCount > 0)
    {
        // mtx.lock();
        {
        // lock_guard函数删除了拷贝构造函数和操作符=重载,类似于scoped_ptr,但保留了构造函数
        // lock_guard<mutex> lock(mtx); // 在这个局部作用域中,程序结束后自动析构,如果中间return了也会析构
        unique_lock<mutex> temp_lock(mtx); // 类似于unique_ptr,虽然删除了拷贝构造函数和操作符=重载,但是扩展了右值引用
        if (tickCount > 0)
        {
                cout << "窗口:" << index << "卖出第" << tickCount << "张票。" << endl;
                tickCount--;
        }
        }
        // mtx.unlock();
        this_thread::sleep_for(chrono::milliseconds(2));
    }
}


unique_lock和condition_variable使用连用:


mutex mtx;
condition_variable cv;
unique_lock<mutex> lck(mtx);
cv.wait(lck); //1、wait的作用使线程进入等待状态;2、lck.unlock可以把mtx给释放掉
// 通知cv上等待的线程,条件成立了,可以往下运行了
//其他在cv上等待的线程,收到通知,从等待状态->阻塞状态->获取互斥锁->线程执行
cv.notify_all();


线程同步通信


多线程编程存在的问题:


1.线程间的互斥;防止资源的访问出现问题。竞态条件 -> 临界区代码段 -> 原子操作 -> 互斥锁mutex(lock_guard、unique_lock)/强两级的无锁实现CAS


2.线程间的同步通信。生产者和消费者线程模型。


互斥


多线程执行共享变量的这段代码可能会导致竞争状态,因此我们将此段代码称为临界区(criticalsection),它是执行共享资源的代码片段,一定不能给多线程同时执行。


所以我们希望这段代码是互斥(mutualexclusion)的,也就说执行临界区(criticalsection)代码段的只能有一个线程,其他线程阻塞等待,达到排队效果。


互斥并不只是针对多线程的竞争条件,同时还可用于多进程,避免共享资源混乱。


同步


互斥解决了「多进程/线程」对临界区使用的问题,但是它没有解决「多进程/线程」协同工作的问题


我们都知道在多线程里,每个线程一定是顺序执行的,它们各自独立,以不可预知的速度向前推进,但有时候我们希望多个线程能密切合作,以实现一个共同的任务。


所谓同步,就是「多进程/线程间」在一些关键点上可能需要互相等待与互通消息,这种相互制约的等待与互通信息称为「进程/线程」同步。


生产者、消费者线程通信


这里涉及到两个线程之间的通信,生产者和消费者线程互相告知,lock_guard无法实现进程间通信这样复杂的工作,所以使用unique_lock和condition_variable进行搭配使用可以实现进程间的通信。代码如下所示:


#include <iostream>
#include <string>
#include<thread>
#include<mutex>
#include<condition_variable>
#include<queue>
using namespace std;
// 定义互斥锁,用户线程间互斥
mutex mtx;
// 定义条件变量,用于线程间的同步通信
condition_variable cv;
// 最常见的问题就是消费者线程消费的更快,生产者线程还没生产出来就开始消费了
class Queue
{
    public:
    void put(int val)
    {
        // lock_guard<mutex> lock(mtx);
        unique_lock<mutex> lck(mtx);
        while(!que.empty())
        {
            // que不为空,生产者应该通知消费者去消费,消费完了在生产
            // 生产者进程应该进入阻塞状态,并把mtx互斥锁
            cv.wait(lck);
        }
        que.push(val);
        /*
        notify_one:通知另外的一个线程
        notify_all:通知另外的所有线程
        */
        // 通知其他的所有线程,生产了一个物品,可以进行消费了,
        // 其他线程得到该通知就会从 等待状态 -> 阻塞状态 -> 获取互斥锁才能继续执行。
        cv.notify_all(); 
        cout << "生产者 生产:" << val << "号物品" << endl;
    }
    int get()
    {
        // lock_guard<mutex> lock(mtx);
        unique_lock<mutex> lck(mtx);
        while(que.empty())
        {
            // 消费者发现que是空的,通知生产者线程生产物品
            // 进入等待状态,把互斥锁mutex进行释放
            cv.wait(lck);
        }
        int val = que.front();
        que.pop();
        cv.notify_all(); //消费完了,通知其他线程进行生产
        cout << "消费者 消费:" << val << "号物品" << endl;
        return val;
    }
    private:
        queue<int> que;
};
void producer(Queue* que)
{
    for (int i = 0; i <= 10;i++)
    {
        que->put(i);
        this_thread::sleep_for(chrono::milliseconds(100));
    }
}
void consumer(Queue* que)
{
    for (int i = 0; i <= 10; i++)
    {
        que->get();
        this_thread::sleep_for(chrono::milliseconds(100));
    }
}
int main()
{
    Queue que;
    thread t1(producer, &que);
    thread t2(consumer,&que);
    t1.join();
    t2.join();
    return 0;
}


CAS操作


互斥锁是比较重的,临界区代码做的事情如果很复杂,互斥锁使用便很麻烦。但是使用CAS来实现某些代码操作的原子特性便是足够了,CAS是无锁的。使用的头文件为atomic,其实本质上也就是将某些类型设置为原子类型变量,导致只有一个线程可以独立使用。如下示例所示:


#include <iostream>
#include <string>
#include<atomic>
#include<list>
#include<thread>
using namespace std;
/*
使用lock_guard实现临界代码段的互斥访问
lock_guard<mutex> lock(mtx);
Count++;
*/
volatile std::atomic_bool isReady = {false};
volatile std::atomic_int number = {0};
void task()
{
    while(!isReady)
    {
        // 让线程让出当前的CPU时间片,等待下一次调度
        this_thread::yield();
    }
    for (int i = 0; i < 100;i++)
    {
        number++;
    }
}
int main()
{
    list<thread> tlist;
    for (int i = 0; i < 10;i++)
    {
        tlist.push_back(thread(task));
    }
    // 让主线程睡眠三秒
    this_thread::sleep_for(chrono::seconds(3));
    cout << "number = " << number << endl;
    isReady = true;
    cout << "number = " << number << endl;
    for(thread &t:tlist)
    {
        t.join();
    }
    cout << "number = " << number << endl;
    return 0;
}
/*output::
number = 0
number = 1000
number = 1000
*/
目录
相关文章
|
15天前
|
缓存 安全 C++
C++无锁队列:解锁多线程编程新境界
【10月更文挑战第27天】
29 7
|
15天前
|
消息中间件 存储 安全
|
1月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
41 1
C++ 多线程之初识多线程
|
21天前
|
存储 并行计算 安全
C++多线程应用
【10月更文挑战第29天】C++ 中的多线程应用广泛,常见场景包括并行计算、网络编程中的并发服务器和图形用户界面(GUI)应用。通过多线程可以显著提升计算速度和响应能力。示例代码展示了如何使用 `pthread` 库创建和管理线程。注意事项包括数据同步与互斥、线程间通信和线程安全的类设计,以确保程序的正确性和稳定性。
|
1月前
|
存储 前端开发 C++
C++ 多线程之带返回值的线程处理函数
这篇文章介绍了在C++中使用`async`函数、`packaged_task`和`promise`三种方法来创建带返回值的线程处理函数。
45 6
|
1月前
|
缓存 负载均衡 Java
c++写高性能的任务流线程池(万字详解!)
本文介绍了一种高性能的任务流线程池设计,涵盖多种优化机制。首先介绍了Work Steal机制,通过任务偷窃提高资源利用率。接着讨论了优先级任务,使不同优先级的任务得到合理调度。然后提出了缓存机制,通过环形缓存队列提升程序负载能力。Local Thread机制则通过预先创建线程减少创建和销毁线程的开销。Lock Free机制进一步减少了锁的竞争。容量动态调整机制根据任务负载动态调整线程数量。批量处理机制提高了任务处理效率。此外,还介绍了负载均衡、避免等待、预测优化、减少复制等策略。最后,任务组的设计便于管理和复用多任务。整体设计旨在提升线程池的性能和稳定性。
74 5
|
1月前
|
C++
C++ 多线程之线程管理函数
这篇文章介绍了C++中多线程编程的几个关键函数,包括获取线程ID的`get_id()`,延时函数`sleep_for()`,线程让步函数`yield()`,以及阻塞线程直到指定时间的`sleep_until()`。
23 0
C++ 多线程之线程管理函数
|
1月前
|
资源调度 Linux 调度
Linux C/C++之线程基础
这篇文章详细介绍了Linux下C/C++线程的基本概念、创建和管理线程的方法,以及线程同步的各种机制,并通过实例代码展示了线程同步技术的应用。
29 0
Linux C/C++之线程基础
|
3月前
|
Java 调度
基于C++11的线程池
基于C++11的线程池
|
4月前
|
算法 编译器 C++
开发与运维线程问题之在C++的原子操作中memory_order如何解决
开发与运维线程问题之在C++的原子操作中memory_order如何解决
43 2