C++像Go一样的并发与闭包

mb600aa3928e8ce · · 373 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

原创 lightcity 光城 2020-04-29

C++像Go一样的并发与闭包

1.并发与并行的区分

  • 并发的关键是你有处理多个任务的能力,不一定要同时。
  • 并行的关键是你有同时处理多个任务的能力。

举例:并发就是一个厕所坑很多人排队交替用,并行就是多个厕所坑多个人用,可以同时。

并发性是程序的一种属性,其中两个或多个任务可以同时进行。并行性是一个运行时属性,其中两个或多个任务同时执行。通过并发性,为程序定义一个适当的结构。并发可以使用并行来完成它的工作,但并行不是并发的最终目标。

2.Go的优雅写法

并发主要由切换时间片来实现“同时”运行,在并行则是直接利用多核实现多线程的运行,但 Go 可以设置使用核数,以发挥多核计算机的能力。

Goroutine 奉行通过通信来共享内存,而不是共享内存来通信。

下面是Go的一个简单例子:

import (
 "fmt"
 "sync"
 "time"
)

func main() {
 page_req, page_rsp := 11
 var wg sync.WaitGroup
 wg.Add(2)
 go func() {
  page_req++
  page_rsp++
  time.Sleep(time.Millisecond * 2000)
  fmt.Println(page_req, page_rsp)
  wg.Done()
 }()

 go func() {
  page_req++
  page_rsp++

  time.Sleep(time.Millisecond * 3000)
  fmt.Println(page_req, page_rsp)
  wg.Done()
 }()
 wg.Wait()
}

3.C++写法

我们预期:

TaskList task_list;

task_list += [&]() {return Foo(req,rsp);};
task_list += [&]() {return Bar(req,rsp);};
task_list.ExeAllTask();

或者:

TaskList task_list;

task_list<<[&]() { return Foo(req,rsp); }<<[&]() { return Bar(req,rsp); };

task_list.ExeAllTask();

给它一个任务池,把每个任务放进去,并发的执行。

首先业务逻辑很简答 ,就是只需要实现Foo,Bar函数就行了。

int Foo(const int&req, int&rsp)
{
    rsp++;
    cout << "doing foo task req: " << req <<" rsp: " <<rsp<< endl;
    return 0;
}
int Bar(const int&req, int&rsp)
{
    rsp++;
    cout << "doing bar task req: " << req<<" rsp: " <<rsp<< endl;
    return 0;
}

紧接着我们看到+=<<操作在c++中就是重载操作符,每次塞进去的是个任务,所以我们命名每个任务为Task。还需要一个池子处理这些任务,命名为TaskList,下面就是实现这两个类。

假设每个业务对应的业务函数时个返回值为int,入参为void类型的,那么我们先做一次这样操作:

using TaskFunc = std::function<int(void)>

Task实现:

class Task
{

protected:
    const TaskFunc task_func_;

public:
    Task(const TaskFunc &func) : task_func_(func)
    {
    }
    int Process()
    
{
        task_func_();
        return 0;
    }
};

紧接着就是需要一个池子,类似中央调度器,去调度任务,也就是执行每个任务的Process方法。

TaskList实现:

class TaskList
{

public:
    std::vector<Task *> task_lists_;
    TaskList()
    {
    }
    ~TaskList()
    {
        for (auto &task : task_lists_)
        {
            delete task;
        }
        task_lists_.clear();
    }
    void operator+=(const TaskFunc &task_func)
    {
        AddTask(task_func);
    }
    TaskList& operator<<(const TaskFunc& task_func) {
        AddTask(task_func);
        return *this;
    }
    void AddTask(const TaskFunc & task_func) {
        Task *task = new Task(task_func);
        task_lists_.push_back(task);
    }
    int ExeAllTask()
    
{
        for (auto &task : task_lists_)
        {
            task->Process();
        }
        return 0;
    }
};

实现起来,重点三个地方。

  • 第一个:执行任务ExecAllTask,调用Task的Proccess方法即可
  • 第二个:操作符重载,支持连续累加

最后就是前面的调用了:

int main()
{
    int req=1,rsp=0;

    TaskList task_list;

    task_list += [&]() {return Foo(req,rsp);};
    task_list += [&]() {return Bar(req,rsp);};
    
    task_list<<[&]() { return Foo(req,rsp); }<<[&]() { return Bar(req,rsp); }; 

    task_list.ExeAllTask();
}

实现起来不算复杂,对比不同语言的实现方式,有利于不断提升。

4.Go闭包

a closure is a record storing a function together with an environment.

闭包是由函数和与其相关的引用环境组合而成的实体 。

闭包究竟包了什么?

  • 函数
    • 指的是在闭包实际实现的时候,往往通过调用一个外部函数返回其内部函数来实现的。内部函数可能是内部实名函数、匿名函数或者一段lambda表达式。用户得到一个闭包,也等同于得到了这个内部函数,每次执行这个闭包就等同于执行内部函数
  • 环境
    • 与其(函数)相关的引用环境

验证一下传递引用与非引用的区别,对上述环境的影响。

func foo_1(x *int) func() {
 return func() {
  *x = *x + 1
  fmt.Printf("foo_1 val = %d\n", *x)
 }
}
func foo_2(x int) func() {
 return func() {
  x = x + 1
  fmt.Printf("foo_1 val = %d\n", x)
 }
}

调用:

x := 10
f_1 := foo_1(&x)
f_2 := foo_2(x)
f_1() // 11
f_1() // 12
f_2() // 11
f_2() // 12

x = 100
f_1() // 101
f_1() // 102
f_2() // 13
f_2() // 14

可以看到前面输出11,12是因为每次调用的时候,因为闭包f_1f_2都保存了x=10时的环境,每次调用闭包f_1f_2都会执行一次自增+打印的内部匿名函数。所以输出是11与12。而当修改值为100的时候,由于f_1传递的是引用,在f_1中的x环境不限制在f_1中,因此会被修改,输出101,102,而f_2则还是之前的值累加。

5.C++像Go一样的闭包

闭包,我们想到了lambda。传入闭包中的元素,必须为其在堆上分配内存,如果以=值传递,那么在外面得分配好,如果以&传递,就不需要再外面提前分配了。

#include <functional>
#include <iostream>
#include <memory>
typedef std::function<int(void)> Fun;
Fun  Test1() {
 // 保证内存在堆上分配及自动回收
    std::shared_ptr<int> t = std::make_shared<int>( 0 );  
 return [=]{
  (*t) += 1;     
  return (*t);
 };
}
int  main()
{
    auto f1 = Test1();
 std::cout << f1() << std::endl ;
 std::cout << f1() << std::endl;
 std::cout << f1() << std::endl;
 auto f2 = Test1();
 std::cout << f2() << std::endl;
 std::cout << f2() << std::endl;
 std::cout << f2() << std::endl;
 return 0;
}

同Go一样的输出。



有疑问加站长微信联系(非本文作者)

本文来自:51CTO博客

感谢作者:mb600aa3928e8ce

查看原文:C++像Go一样的并发与闭包

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

373 次点击  
加入收藏 微博
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传