前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C++像Go一样的并发与闭包

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

作者头像
公众号guangcity
发布2020-05-09 17:09:07
4890
发布2020-05-09 17:09:07
举报
文章被收录于专栏:光城(guangcity)光城(guangcity)

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

1.并发与并行的区分

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

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

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

2.Go的优雅写法

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

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

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

代码语言:javascript
复制
import (
 "fmt"
 "sync"
 "time"
)

func main() {
 page_req, page_rsp := 1, 1
 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++写法

我们预期:

代码语言:javascript
复制
TaskList task_list;

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

或者:

代码语言:javascript
复制
TaskList task_list;

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

task_list.ExeAllTask();

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

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

代码语言:javascript
复制
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类型的,那么我们先做一次这样操作:

代码语言:javascript
复制
using TaskFunc = std::function<int(void)>

Task实现:

代码语言:javascript
复制
class Task
{
protected:
    const TaskFunc task_func_;

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

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

TaskList实现:

代码语言:javascript
复制
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方法即可
  • 第二个:操作符重载,支持连续累加

最后就是前面的调用了:

代码语言:javascript
复制
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表达式。用户得到一个闭包,也等同于得到了这个内部函数,每次执行这个闭包就等同于执行内部函数
  • 环境
    • 与其(函数)相关的引用环境

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

代码语言:javascript
复制
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)
 }
}

调用:

代码语言:javascript
复制
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。传入闭包中的元素,必须为其在堆上分配内存,如果以=值传递,那么在外面得分配好,如果以&传递,就不需要再外面提前分配了。

代码语言:javascript
复制
#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一样的输出。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-04-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 光城 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • C++像Go一样的并发与闭包
    • 1.并发与并行的区分
      • 2.Go的优雅写法
        • 3.C++写法
          • 4.Go闭包
            • 5.C++像Go一样的闭包
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档