C++并发编程 -3.同步并发操作

news2024/11/28 10:39:47

本文介绍如何使用条件变量控制并发的同步操作、C++ 并发三剑客,函数式编程

一.条件变量

1.概念

        C++条件变量(condition variable)是一种多线程编程中常用的同步机制,用于线程间的通信和协调。它允许一个或多个线程等待某个条件的发生,当条件满足时,线程被唤醒并继续执行。

        在C++中,条件变量通常与互斥锁(mutex)一起使用,以确保线程之间的安全访问共享资源。互斥锁用于保护共享资源的访问,而条件变量用于在某个条件满足时通知等待的线程。

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

std::mutex mtx;
std::condition_variable cv;
bool isReady = false;

void workerThread()
{
    std::unique_lock<std::mutex> lock(mtx);
    while (!isReady)
    {
        cv.wait(lock); // 等待条件变量满足
    }
    std::cout << "Worker thread is awake!" << std::endl;
}

int main()
{
    std::thread worker(workerThread);

    std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟一些耗时操作

    {
        std::lock_guard<std::mutex> lock(mtx);
        isReady = true;
        cv.notify_one(); // 唤醒等待的线程
    }
    worker.join();
    return 0;
}

        主线程创建了一个工作线程,并在工作线程中等待条件变量isReadytrue。主线程在等待一段时间后,将isReady设置为true,并通过cv.notify_one()唤醒工作线程。工作线程在等待期间调用cv.wait(lock),这会使线程进入等待状态,直到条件变量被唤醒。一旦条件满足,工作线程被唤醒并输出消息。

        其中wait参数有三种使用方式:

waitwait_forwait_until
函数原型void wait(std::unique_lock<std::mutex>& lock);template< class Rep, class Period > std::cv_status wait_for(std::unique_lock<std::mutex>& lock, const std::chrono::duration<Rep, Period>& rel_time);template< class Clock, class Duration > std::cv_status wait_until(std::unique_lock<std::mutex>& lock, const std::chrono::time_point<Clock, Duration>& abs_time);
功能调用wait释放锁,等待notify。被唤醒后加锁

1.调用wait释放锁,等待notify。被唤醒后加锁。

2.等待时间超过指定的相对时间,超时后继续加锁。

1.调用wait释放锁,等待notify。被唤醒后加锁。

2.等待时间超过指定的相对时间,无论是否超时仍加锁。

3.等待超过指定时间

返回值

等待时间内被唤醒,则返回std::cv_status::no_timeout;

如果等待时间超时,则返回std::cv_status::timeout

与waitfor一致
示例
cv.wait(lock, []{ return isDataReady; });

cv.wait_for(lock, std::chrono::seconds(5), []{ return isDataReady; }

    auto timeout = std::chrono::steady_clock::now() + std::chrono::seconds(5);
    cv.wait_until(lock, timeout, []{ return isDataReady; })

2.使用条件变量构建线程安全的栈区

        回顾

        在第三章节 -构建线程安全的栈区  为了避免数据竞争,对于pop操作在线程中调用empty判断是否为空,如果不为空,则pop,因为empty和pop内部分别加锁,是两个原子操作,导致pop时可能会因为其他线程提前pop导致队列为空,从而引发崩溃。

void push(T new_value)
{
    std::lock_guard<std::mutex> lock(m);
    data.push(std::move(new_value));
}

T pop()
{
    std::lock_guard<std::mutex> lock(m);
    auto element = data.top();
    data.pop();
    return element;
}

threadsafe_stack1<int> safe_stack;
safe_stack.push(1);
std::thread t1([&safe_stack]() 
{
    if (!safe_stack.empty()) 
    {
       std::this_thread::sleep_for(std::chrono::seconds(1));
       safe_stack.pop();
    }
});
std::thread t2([&safe_stack]() 
{
    if (!safe_stack.empty()) 
    {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        safe_stack.pop();
    }
});

                 

        通过添加《传入引用》或《返回弹出元素的指针》来解决上面异常。但是两种方式都存在一定的弊端:

        传入引用弊端是需要在函数外部构建临时变量影响效率且需要抛出空栈。

        返回智能指针异常下会导致空指针产生。

        这么都不是很友好,所以我们可以通过条件变量完善之前的程序,重新实现一个线程安全队列。

#include <iostream>
#include <thread>
#include <stack>
#include <memory>
#include <mutex>
#include <condition_variable>

using namespace std;

template<typename T>
class threadQueue
{
public:
    threadQueue(): mutx(), condVar(), stackQueue()
    {};
    ~threadQueue(){};

    void push(T value)
    {
        lock_guard<mutex> lk(mutx);
        stackQueue.push(value);
        condVar.notify_one();
    }

    void waitAndpop(T &stackQueue_)
    {
        unique_lock<mutex> lk(mutx);
        /*
           wait函数第二个参数确保队列有数据才进行条件变量后续操作,相当于增加一层判断,更加准确
        */
        condVar.wait(lk, [this](){return !stackQueue.empty();});
        stackQueue_ = stackQueue.top();
        stackQueue.pop();
    }
    shared_ptr<T> waitAndPop()
    {
        unique_lock<mutex> lk(mutx);
        condVar.wait(lk, [this](){return !stackQueue.empty();});
        shared_ptr<T> res = make_shared<T>(stackQueue.top());
        stackQueue.pop();
        return res;
    }

    bool tryPop(T &value)
    {
        lock_guard<mutex> lk(mutx);
            if(stackQueue.empty()){return false;}
        value = stackQueue.top();
        stackQueue.pop();
        return true;
    }
    shared_ptr<T> tryPop()
    {
        lock_guard<mutex> lk(mutx);
        if (stackQueue.empty())
        	return std::shared_ptr<T>();
        shared_ptr<T> res = make_shared<T>(stackQueue.top());
        stackQueue.pop();
        return res;
    }

private:
    mutable mutex mutx;
    condition_variable condVar;
    stack<T> stackQueue;
};

mutex mutxThread;

template<typename T>
void funcProducer(threadQueue<T> &thdQueue)
{
    for(;;)
    {
        for(size_t i = 0; i < __LONG_MAX__; i++)
        {
            thdQueue.push(i);
            lock_guard<mutex> lk(mutxThread);
            cout<<"funcProducer:"<<i<<endl;
            this_thread::sleep_for(chrono::milliseconds(100));
        }
    }
}

template<typename T>
void funcWorker1(threadQueue<T> &thdQueue)
{
    for(;;)
    {
        auto data = thdQueue.waitAndPop();

        lock_guard<mutex> lk(mutxThread);
        cout<<"funcWorker1 waitAndpop:"<<*(data)<<endl;
        // this_thread::sleep_for(chrono::milliseconds(500));
    }
}

template<typename T>
void funcWorker2(threadQueue<T> &thdQueue)
{
    for(;;)
    {
        auto data = thdQueue.tryPop();
        if(data != nullptr)
        {
            lock_guard<mutex> lk(mutxThread);
            cout<<"funcWorker2 waitAndpop:"<<*(data)<<endl;
        }
        // this_thread::sleep_for(chrono::milliseconds(500));
    }
}

int main()
{
    threadQueue<int> thdQueue;

    thread t1(funcProducer<int>, ref(thdQueue));
    thread t2(funcWorker1<int>, ref(thdQueue));
    thread t3(funcWorker2<int>, ref(thdQueue));

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

二.C++ 并发三剑客

        简单介绍一下future, promise和async三者之间的关系。其具体实现请看本章节第四小节

1.async

        std::async 是一个用于异步执行函数的模板函数,它返回一个 std::future 对象,该对象用于获取函数的返回值。其主要应用于I/O密集型任务,如网络请求或文件读写,其中操作可能需要等待,但CPU不需要做太多工作.(thread适用于CPU密集型任务)

        async可选填参数:

        指定launch::async:表示任务执行在另一线程。

        指定launch::deferred:表示延迟执行任务,调用get或者wait时才会执行,不会创建线程,惰性执行在当前线程。

        不填写或同时指定:属于未定义行为,编译器会根据实际情况决定采用哪种策略(通常创建线程)

1.1 async异步任务

        async启用异步执行后,在其内部会创建一个线程,任务完成后线程自动销毁,不需要join等操作。


#include <iostream>
#include <thread>
#include <memory>
#include <future>

using namespace std;

void func()
{
    int i = 0;
    for(;;)
    {
        cout<<"func"<<endl;
        this_thread::sleep_for(chrono::seconds(1));
        if(++i == 10) {break;}
    }
    cout<<" thread end"<<endl;
}

int main()
{
    std::future<void> the_answer=std::async(launch::async, func);//deferred

    for(int i = 0; i < 15; ++i)
    {
        cout<<"main run"<<endl;
        this_thread::sleep_for(chrono::seconds(1));
    }

    return 1;
}

  刚运行时可以看到存在两个线程

  线程退出后:

1.2 async延时执行

        当指定launch::deferred表示延迟执行任务,调用get或者wait时才会在调用线程中执行。                 std::future::get() 和 std::future::wait() 是 C++ 中用于处理异步任务的两个方法.

1.2.1 future::get()

        这是一个阻塞调用,用于获取 std::future 对象表示的值或异常。如果异步任务还没有完成,get() 会阻塞当前线程,直到任务完成。如果任务已经完成,get() 会立即返回任务的结果。get() 只能调用一次,因为它会移动或消耗掉 std::future 对象的状态。一旦 get() 被调用,std::future 对象就不能再被用来获取结果。如果调用两次,会出现如下结果:

#include <iostream>
#include <thread>
#include <memory>
#include <future>

using namespace std;

void func()
{
    cout<<"func"<<endl;
}

int main()
{
    std::future<void> the_answer=std::async(launch::deferred, func);//deferred

    cout<<"main run"<<endl;
    this_thread::sleep_for(chrono::seconds(1));
    the_answer.get();
    the_answer.get();

    return 1;
}

                 

        为避免多次调用get造成异常,尽量使用wait

1.2.2 future::wait()

        std::future::wait() 也是一个阻塞调用,但它与 get() 的主要区别在于 wait() 不会返回任务的结果。它只是等待异步任务完成。如果任务已经完成,wait() 会立即返回。如果任务还没有完成,wait() 会阻塞当前线程,直到任务完成。与 get() 不同,wait() 可以被多次调用,它不会消耗掉 std::future 对象的状态。

#include <iostream>
#include <thread>
#include <memory>
#include <future>

using namespace std;

int func()
{
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    cout<<"func"<<endl;
    return 10;
}

int main()
{
    future<int> the_answer = async(launch::deferred, func);
    cout<<"main run"<<endl;
    
    auto start = std::chrono::high_resolution_clock::now(); 
    the_answer.wait();
    the_answer.wait();
    auto end = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start); 
    cout << "the_answer.wait run time: " << duration.count() << " ms" << endl;

    return 1;
}

         

可以看到只运行了1000ms, 当第一个wait运行结束表示任务执行结束,而第二个wait会立马返回。

waitget
阻塞线程,知道任务运行完毕阻塞线程,知道任务运行完毕
可多次调用,当任务执行完毕后wait将不在起作用,会立马返回。不可多次调用,异常
执行任务,不获取结果执行任务,获取结果
  •  也可以通过wait_for(std::chrono::seconds(0)(要求任务立马返回),判断任务是否已经完成
if (the_answer.wait_for(std::chrono::seconds(0)) == std::future_status::ready)
{
   cout<<"task already over"<<endl;
   return 0;
}

2.promise和packaged_task

        promise和packaged_task都是C++中的异步编程工具,用于在多线程环境下进行任务的分离和结果的传递。

        promise是一种用于在一个线程中产生结果,并在另一个线程中获取结果的机制。它允许一个线程(称为"提供者")在某个时间点设置一个值或异常,而另一个线程(称为"消费者")在需要时获取该值或异常。

        packaged_task是一种将可调用对象(如函数、函数对象或Lambda表达式)封装为一个可以异步执行的任务的机制。

        两者都是与future结合使用,通过future获取结果或者值。

2.1.future与packaged_task

        packaged_task是一个可调用目标,它包装了一个任务,该任务可以在另一个线程上运行。它可以捕获任务的返回值或异常,并将其存储在future对象中,以便以后使用。

        使用future和packaged_task通常的做法是:

1)  创建一个std::packaged_task对象,该对象包装了要执行的任务。

funciton my_task
packaged_task<int()> task(my_task);

2)  调用packaged_task对象的get_future()方法,该方法返回一个与任务关联的future对象。

future<int> result = task.get_future();

3)  在另一个线程上调用std::packaged_task对象的operator(),以执行任务。

 thread t(move(task));

4)   在需要任务结果的地方,调用与任务关联的future对象的get()方法,以获取任务的返回值或异常。

int value = result.get();

示例:

#include <iostream>
#include <thread>
#include <memory>
#include <future>

using namespace std;

int my_task() {
    this_thread::sleep_for( chrono::seconds(5));
    cout << "my task run 5 s" <<  endl;
    return 42;
}
int main()
{
    // 创建一个包装了任务的  packaged_task 对象  
    packaged_task<int()> task(my_task);
    // 获取与任务关联的  future 对象  
    future<int> result = task.get_future();
    // cout<<"task.get_future()"<<task.get_future().get()<<endl;
    // 在另一个线程上执行任务  
    thread t(move(task));
    t.detach(); // 将线程与主线程分离,以便主线程可以等待任务完成  
    // 等待任务完成并获取结果  
    cout << "will get result"<<endl;
    int value = result.get();
    cout << "The result is: " << value <<  endl;

}

                         

        在上面的示例中,我们创建了一个包装了任务的std::packaged_task对象,并获取了与任务关联的std::future对象。然后,我们在另一个线程上执行任务,并等待任务完成并获取结果。最后,我们输出结果。

2.2.future与promise
2.2.1 设置值

        packaged_task主要是异步过程中任务,promise在异步过程中处理数值。

#include <iostream>
#include <thread>
#include <future>

void set_value(std::promise<int>& prom) {
    std::this_thread::sleep_for(std::chrono::seconds(2));
    // 设置结果为42
    prom.set_value(42);
}

int main() {
    std::promise<int> prom;
    std::future<int> fut = prom.get_future();
    
    std::thread t(set_value, std::ref(prom));
    // 在主线程中等待结果
    int result = fut.get();
    std::cout << "Result: " << result << std::endl;
    t.join();
    
    return 0;
}

        值得注意的是在调用fut.get()方法时,如果promise的值还没有被设置,则该方法会阻塞当前线程,直到值被设置为止。

2.2.2 设置异常

        promise还有一个set_exception()方法,用于设置异常。该方法接受std::exception_ptr参数,该参数可以通过调用std::current_exception()方法获取。


#include <iostream>
#include <thread>
#include <future>

void set_exception(std::promise<int>& prom) {
    try {
        // 模拟一些可能抛出异常的操作
        throw std::runtime_error("Something went wrong");
    } catch (...) {
        // 捕获异常并设置到 promise 中
        prom.set_exception(std::current_exception());
    }
}

int main() {
    std::promise<int> prom;
    std::future<int> fut = prom.get_future();
    
    std::thread t(set_exception, std::ref(prom));
    
    try {
        // 在主线程中等待结果
        int result = fut.get();
        std::cout << "Result: " << result << std::endl;
    } catch (const std::exception& e) {
        std::cout << "Exception caught: " << e.what() << std::endl;
    }
    
    t.join();
    
    return 0;
}

        set_exception 函数中,我们模拟了一些可能抛出异常的操作,并使用prom.set_exception (std::current_exception()) 将当前捕获的异常设置到 promise 中。在主线程中,我们使用 fut.get() 等待并获取结果。如果在另一个线程中发生了异常,我们可以在主线程中使用 try-catch 块捕获并处理异常。

3 future与shared_future

        当需要多个线程等待同一个执行结果时,需要使用std::shared_future。

        适合使用std::shared_future的场景,多个线程等待同一个异步操作的结果:

#include <iostream>
#include <thread>
#include <future>

void myFunction(std::promise<int>&& promise) 
{
    std::this_thread::sleep_for(std::chrono::seconds(1));
    promise.set_value(42); // 设置 promise 的值
}
void threadFunction(std::shared_future<int> future) 
{
    try {
        int result = future.get();
        std::cout << "Result: " << result<<"this thread id:"<<std::this_thread::get_id() << std::endl;
    }
    catch (const std::future_error& e) {
        std::cout << "Future error: " << e.what() << std::endl;
    }
}
int main() 
{
    std::promise<int> promise;
    std::shared_future<int> future = promise.get_future();
    std::thread myThread1(myFunction, std::move(promise)); // 将 promise 移动到线程中
    // 使用 share() 方法获取新的 shared_future 对象  
    std::thread myThread2(threadFunction, future);
    std::thread myThread3(threadFunction, future);
    myThread1.join();
    myThread2.join();
    myThread3.join();

    return 0;
}

         

        在这个示例中,创建了一个std::promise<int>对象promise和一个与之关联的std::shared_future<int>对象future。然后,我们将promise对象移动到另一个线程myThread1中,该线程将执行myFunction函数,并在完成后设置promise的值。我们还创建了两个线程myThread2myThread3,它们将等待future对象的结果。如果myThread1成功地设置了promise的值,那么future.get()将返回该值。这些线程可以同时访问和等待future对象的结果,而不会相互干扰。 

4. 自顶向下实现

4.1  用packaged_task实现async

        async异步任务相当于创建线程,不再予以实现。

        async延时执行示例如下:

#include <iostream>
#include <thread>
#include <future>
#include <functional>

using Func = std::function<int(int)>;

std::future<int> myAsync(Func task, int i)
{
    std::packaged_task<int(int)> packaged(task);
    std::future<int> fu = packaged.get_future();

    std::thread t(std::move(packaged), i);
    t.detach();
    return fu;
}

int main()
{
    auto task = [](int i) { std::this_thread::sleep_for(std::chrono::seconds(1)); return i+100; };

    std::future<int> f = myAsync(task, 5);
    std::cout << f.get() << std::endl;
    return 0;
}

         async指定deferred 表示延时进行,其内部与上述内部实现类似,先使用packaged打包需要执行的任务,然后将任务与future绑定,将任务move到线程中,执行完毕后通过future.get获取任务执行结果。如此便满足第二章节图片内容结构。

4.2  使用promise实现packaged_task

          通过promise设置值来完成packaged_task反馈。

#include <iostream>
#include <thread>
#include <future>
#include <functional>

//typename关键字在C++中的主要作用是告诉编译器某个名称是一个类型而不是变量或函数。
template<typename Type, typename... Args>
class myPackTask
{
public:
    myPackTask(std::function<Type(Args...)> _func):func_(_func){}

    std::future<Type> get_future()
    {
        return promise_.get_future();
    }

    void operator()(Args... args)
    {
        Type result = func_(args...);
        promise_.set_value(result);
    }

private:
    std::function<Type(Args...)> func_;
    std::promise<Type> promise_;
};

int add(int a, int b) {
    return a + b;
}

int main() {
    myPackTask<int, int, int> task(add);
    std::future<int> future = task.get_future(); 

    std::thread thread(std::move(task), 2, 3);  
    int result = future.get();  
    std::cout << "Result: " << result << std::endl;

    thread.join();  // 等待线程结束

    return 0;
}
 4.3  实现promise

        其主要思想就是通过条件变量通知get所在线程调用的地方。

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

template<typename T>
class Promise {
public:
    Promise() : value(nullptr), ready(false) {}

    void set(const T& val) {
        std::unique_lock<std::mutex> lock(mutex);
        value = std::make_shared<T>(val);
        ready = true;
        condition.notify_all();
    }

    T get() {
        std::unique_lock<std::mutex> lock(mutex);
        condition.wait(lock, [this] { return ready; });
        return *value;
    }

private:
    std::shared_ptr<T> value;
    bool ready;
    std::mutex mutex;
    std::condition_variable condition;
};

int main() {
    Promise<int> promise;

    std::thread producer([&promise]() {
        std::this_thread::sleep_for(std::chrono::seconds(2));
        promise.set(42);
    });

    std::thread consumer([&promise]() {
        int result = promise.get();
        std::cout << "Got value: " << result << std::endl;
    });

    producer.join();
    consumer.join();

    return 0;
}

三.并行与函数式编程

以下内容摘自恋恋风尘,博主讲解C++并发非常不错,力荐

1 快速排序

        先使用C++ 设计一个快速排序方法

#include <iostream>
#include <thread>
#include <memory>
#include <future>

template<typename T>
void quick_sort_recursive(T arr[], int start, int end) {
    if (start >= end) return;
    T key = arr[start];
    int left = start, right = end;
    while(left < right) {
        while (arr[right] >= key && left < right) right--;
        while (arr[left] <= key && left < right) left++;
        std::swap(arr[left], arr[right]);
    }
    if (arr[left] < key) {
        std::swap(arr[left], arr[start]);
    }
    quick_sort_recursive(arr, start, left - 1);
    quick_sort_recursive(arr, left + 1, end);
}
template<typename T>
void quick_sort(T arr[], int len) {
    quick_sort_recursive(arr, 0, len - 1);
}

int main() {
    int num_arr[] = { 5,3,7,6,4,1,0,2,9,10,8 };
    int length = sizeof(num_arr) / sizeof(int);
     quick_sort(num_arr, length );
    std::cout << "sorted result is ";
    for (int i = 0; i < length; i++) {
        std::cout << " " << num_arr[i];
    }
    std::cout << std::endl;    
}

2 使用函数式编程快速排序

        函数式编程可以被看作是将函数视为数学中的公式。在函数式编程中,函数被视为一种纯粹的数学映射,它接受输入并产生输出,没有副作用或可变状态。这种思想源于数学中的函数概念,其中函数被定义为将输入映射到输出的关系。

        在第一小节实现快速排序是一种面向过程的编程方式,我们可以通过定义一个函数来实现快速排序算法。这个函数接受一个数组作为输入,并通过一系列的步骤来实现快速排序的逻辑。这些步骤包括选择基准元素、划分数组、递归调用等。

        而使用函数式编程,强调使用纯函数来构建程序,通过函数的组合和转换来处理数据。函数式编程避免使用可变状态和共享状态。

#include <iostream>
#include <thread>
#include <memory>
#include <future>
#include <list>
#include <algorithm>

template<typename T>
std::list<T> sequential_quick_sort(std::list<T> input)
{
    if (input.empty())
    {
        return input;
    }
    std::list<T> result;
    //  ① 将input中的第一个元素放入result中,并且将这第一个元素从input中删除
    result.splice(result.begin(), input, input.begin());  
    //  ② 取result的第一个元素,将来用这个元素做切割,切割input中的列表。
    T const& pivot = *result.begin();    
    //  ③std::partition 是一个标准库函数,用于将容器或数组中的元素按照指定的条件进行分区,
    // 使得满足条件的元素排在不满足条件的元素之前。
    // 所以经过计算divide_point指向的是input中第一个大于等于pivot的元素
        auto divide_point = std::partition(input.begin(), input.end(),
            [&](T const& t) {return t < pivot; });    
    // ④ 我们将小于pivot的元素放入lower_part中
    std::list<T> lower_part;
    lower_part.splice(lower_part.end(), input, input.begin(),
        divide_point);  
    // ⑤我们将lower_part传递给sequential_quick_sort 返回一个新的有序的从小到大的序列
    //lower_part 中都是小于divide_point的值
        auto new_lower(
            sequential_quick_sort(std::move(lower_part)));    
    // ⑥我们剩余的input列表传递给sequential_quick_sort递归调用,input中都是大于divide_point的值。
        auto new_higher(
            sequential_quick_sort(std::move(input)));    
        //⑦到此时new_higher和new_lower都是从小到大排序好的列表
        //将new_higher 拼接到result的尾部
        result.splice(result.end(), new_higher);    
        //将new_lower 拼接到result的头部
        result.splice(result.begin(), new_lower);   
        return result;
}

int main() {
    std::list<int> numlists = { 6,1,0,7,5,2,9,-1 };
    auto sort_result = sequential_quick_sort(numlists);
    std::cout << "sorted result is ";
    for (auto iter = sort_result.begin(); iter != sort_result.end(); iter++) {
        std::cout << " " << (*iter);
    }
    std::cout << std::endl;
}

3 并行处理快速排序

        在快速排序中,我们以某个数字作为基准(一般开头), 分为前半部分和后半部分,然后前半部分和后半部分单独计算。 

        并行排序的思想是将前半部分(或后半部分)单独拷贝一份,置于async中(不指定启动方式,编译器会根据计算次数决定什么时候异步执行什么时候串行),进行并行计算

#include <iostream>
#include <thread>
#include <memory>
#include <future>
#include <list>
#include <algorithm>

template<typename T>
std::list<T> parallel_quick_sort(std::list<T> input)
{
    if (input.empty())
    {
        return input;
    }
    std::list<T> result;
    result.splice(result.begin(), input, input.begin());
    T const& pivot = *result.begin();
    auto divide_point = std::partition(input.begin(), input.end(),
        [&](T const& t) {return t < pivot; });
    std::list<T> lower_part;
    lower_part.splice(lower_part.end(), input, input.begin(),
        divide_point);
    // ①因为lower_part是副本,所以并行操作不会引发逻辑错误,这里可以启动future做排序
    std::future<std::list<T>> new_lower(
        std::async(&parallel_quick_sort<T>, std::move(lower_part)));
    // ②
    auto new_higher(
        parallel_quick_sort(std::move(input)));    
        result.splice(result.end(), new_higher);    
        result.splice(result.begin(), new_lower.get());    
        return result;
}

int main() 
{
    std::list<int> numlists = { 6,1,0,7,5,2,9,-1 };
    auto sort_result = parallel_quick_sort(numlists);
    std::cout << "sorted result is ";
    for (auto iter = sort_result.begin(); iter != sort_result.end(); iter++) {
        std::cout << " " << (*iter);
    }
    std::cout << std::endl;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1456111.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据脱敏(四)脱敏算法-替换算法

脱敏算法篇使用阿里云数据脱敏算法为模板,使用算子平台快速搭建流程来展示数据 "替换脱敏"是一种数据处理技术&#xff0c;主要用于保护个人隐私和数据安全。它通过将敏感信息&#xff08;如姓名、身份证号、电话号码等&#xff09;替换为无意义或随机的字符&#xf…

hal/SurfaceFlinger/perfetto实战需求问题探讨作业-千里马framework开发

背景 hi&#xff0c;粉丝朋友们&#xff1a; 在新课halperfettosurfaceflinger https://mp.weixin.qq.com/s/LbVLnu1udqExHVKxd74ILg 推出后&#xff0c;各位学员朋友们都积极响应&#xff0c;开始马不停蹄的学习&#xff0c;学员学习后希望有更多的实战案例或者项目拿来练手&…

vue3中基于路由层面和组件层面进行用户角色及权限控制的方法解析

文章目录 一、权限控制二、路由层面控制三、组件层面控制1、使用自定义指令2、使用方法控制3、封装一个权限控制组件来实现组件层面控制权限3.1、组件页面 Authority.vue3.2、使用页面 app.vue3.3、效果预览 一、权限控制 随着前端技术的不断发展&#xff0c;越来越多的前端框…

美食推荐|美食推荐小程序|基于微信小程序的美食推荐系统设计与实现(源码+数据库+文档)

美食推荐小程序目录 目录 基于微信小程序的美食推荐系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、前台功能模块 2、后台功能模块 &#xff08;1&#xff09;用户信息管理 &#xff08;2&#xff09;水果信息管理 &#xff08;3&#xff09;水果类型管理…

投票助力 | 第19届(2023)数智招标采购行业年度评选网络投票已开启!

为展示招标采购领域业务发展的突出成绩和数智化应用创新成果&#xff0c;以优秀企业、个人和案例激励并引导更多的行业实践者们共同努力&#xff0c;推进招标采购行业数智化创新发展&#xff0c;必联网、机电产品招标投标电子交易平台、隆道平台、隆道研究院联合主办了“第19届…

讲技巧乘机会

恋爱季节 - 邓丽君 (Teresa Teng) 词&#xff1a;邓丽君 曲&#xff1a;いずみたく 太阳高天气真晴朗 微风里飘来野花香 手拉手并肩向前走 我和你来到小溪旁 爬过了山坡又村庄 经过了小桥和池塘 我和你歌唱在山岗 青山绿水回声响 谈恋爱要有勇气 要赶快 讲技巧乘机…

HTML 入门指南

简述 参考&#xff1a;HTML 教程- (HTML5 标准) HTML 语言的介绍、特点 HTML&#xff1a;超级文本标记语言&#xff08;HyperText Markup Language&#xff09; “超文本” 就是指页面内可以包含图片、链接等非文字内容。“标记” 就是使用标签的方法将需要的内容包括起来。…

当excel中表格打印预览右边超出限定页面时,调整列宽

解决办法&#xff1a;调整整体列或者部分列的列宽 操作流程如下&#xff1a; 第一步&#xff1a;选中需要调整的列 ①将鼠标放在表格的列上&#xff0c;等出现向下粗箭头后——>②单击&#xff08;变成粗十字&#xff09;该列——>③拖动选中列 第二步&#xff1a;调…

docker ubuntu tomcat 换源 安装软件

第一种办法参考docker中ubuntu容器更换apt源_ubuntu更改apt源 with dockerfile-CSDN博客 sed -i s/archive.ubuntu.com//mirrors.aliyun.com/g /etc/apt/sources.list sed -i s/security.ubuntu.com//mirrors.aliyun.com/g /etc/apt/sources.list apt update apt install vim…

C++学习Day06之继承中的构造与析构

目录 一、程序及输出1.1 构造、析构顺序1.2 默认构造函数1.3 显示调用父类中的其他构造函数 二、分析与总结 一、程序及输出 1.1 构造、析构顺序 先调用父类构造&#xff0c;再调用其他成员构造&#xff0c; 再调用自身构造 &#xff0c;析构的顺序与构造相反 #include<i…

Linix与Windows上使用nc命令测试某一个服务器端口网络是否正常可访问详细安装及测试步骤

一、windows 1、下载nc安装包 https://nszyf.lanzoum.com/ihtqS0v0lwwh 2、下载后解压放置在自己电脑合适的位置&#xff0c;并且配置到环境变量中 3、配置成功环境变量&#xff0c;winr打开运行&#xff0c;输入cmd&#xff0c;回车&#xff0c;打开一个终端测试 测试成功…

MySQL性能分析1——查看频次

1、查看执行频次 查看当前数据库的INSERT,UPDATE,DELETE,SELECT的访问频次&#xff0c;得到当前数据库是以插入&#xff0c;更新和删除为主还是以查询为主&#xff0c;如果是以插入&#xff0c;更新和删除为主的话&#xff0c;那么优化比重可以轻一点儿。 语法&#xff1a; …

初次安装Android Studio卡在gradle的解决方法

原因 国外的下载的地址无法访问才导致无法下载 解决方案 找到新建项目的保存位置找到gradle文件夹 进入文件夹 用文本打开 如图 大概一样&#xff0c;将国外地址改为国内地址 选中的这一条 国内的地址有 腾讯云提供了 Gradle 的国内镜像&#xff0c;您可以通过访问腾讯云…

300分钟吃透分布式缓存-02讲:如何根据业务来选择缓存模式和组件?

缓存读写模式 如下图&#xff0c;业务系统读写缓存有 3 种模式&#xff1a; & Cache Aside&#xff08;旁路缓存&#xff09; & Read/Write Through&#xff08;读写穿透&#xff09; & Write Behind Caching&#xff08;异步缓存写入&#xff09; Cache Asid…

安装 Windows 10

1.镜像安装 镜像安装:安装Windows 10 2.安装过程(直接以图的形式呈现) 选择专业版的 等待安装即可

Android MMKV 接入+ 替换原生 SP + 原生 SP 数据迁移

背景:项目中一直使用的是原生 SP,众所周知,使用原生 SP 存在卡顿性能问题。公司的性能监控平台抓到不少原生 SP 导致的 ANR 问题: java.io.FileDescriptor.sync (FileDescriptor.java) android.os.FileUtils.sync (FileUtils.java:256) android.app.SharedPreferencesImpl.…

【SpringBoot3】Spring Security 核心概念

一、什么是 Spring Security 注&#xff1a;本文基于Spring Boot 3.2.1 以及 Spring Security 6.2.1 Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean&#xff0c;充分利用了…

Vue | (一)Vue核心(下) | 尚硅谷Vue2.0+Vue3.0全套教程

文章目录 &#x1f4da;class与style绑定&#x1f4da;条件渲染&#x1f4da;列表渲染&#x1f407;基本列表&#x1f407;key的原理&#x1f407;列表过滤&#xff08;搜索&#xff09;&#x1f407;列表排序&#x1f407;Vue数据监测 &#x1f4da;收集表单数据&#x1f4da;…

2024/2/18:IO进程线程

作业1&#xff1a;使用fgets统计给定文件的行数 代码&#xff1a; #include <stdio.h> #include <string.h> #include <stdlib.h> int main(int argc, const char *argv[]) {//定义FILE *类型的句柄FILE *fpNULL;//判断是否进行了外部传参if(argc ! 2){pri…

JRT监听-PDF-Excel-Img

依赖全新设计&#xff0c;我们无需再顾虑历史兼容性的束缚&#xff1b;同时&#xff0c;基于多年来累积的深入需求理解&#xff0c;JRT监听机制巧妙地借助CMD命令模式&#xff0c;达成了监听的全面统一。无论是PDF、Excel还是图片文件&#xff0c;都不再需要特殊对待或额外区分…