多线程学习 c_在5分钟内学习C ++多线程

多线程学习 c

摘要 :以非常详细的方式介绍了C ++ 14多线程结构的速成课程

新的C ++多线程结构非常易于学习。 如果您熟悉C或C ++并想开始编写多线程程序,那么本文适合您!

我使用C ++ 14作为参考,但是C ++ 17也支持我所描述的内容。 我只介绍常见的构造。 阅读本文之后,您应该能够编写自己的多线程程序。

更新(2020年3月):

我为此制作了一个视频。 您可以在这里观看:

创建线程

可以通过几种方式创建线程:

  1. 使用函数指针
  2. 使用函子
  3. 使用lambda函数

这些方法非常相似,只有很小的区别。 接下来,我将解释每种方法及其差异。

使用函数指针

考虑以下函数,该函数采用向量参考v ,对结果acm的参考以及向量v中的两个索引。 该函数在beginIndexendIndex之间添加所有元素。

void accumulator_function2 ( const std :: vector < int > &v, unsigned long long &acm, 
                            unsigned int beginIndex, unsigned int endIndex)
 {
    acm = 0 ;
    for ( unsigned int i = beginIndex; i < endIndex; ++i)
    {
        acm += v[i];
    }
}

A function calculating the sum of all elements between beginIndex and endIndex in a vector v

现在假设您要将向量分为两部分,并在单独的线程t1t2计算每个部分的总和:

//Pointer to function
    {
        unsigned long long acm1 = 0 ;
        unsigned long long acm2 = 0 ;
        std :: thread t1 (accumulator_function2, std ::ref(v), 
                        std ::ref(acm1), 0 , v.size() / 2 ) ;
        std :: thread t2 (accumulator_function2, std ::ref(v), 
                        std ::ref(acm2), v.size() / 2 , v.size()) ;
        t1.join();
        t2.join();

        std :: cout << "acm1: " << acm1 << endl ;
        std :: cout << "acm2: " << acm2 << endl ;
        std :: cout << "acm1 + acm2: " << acm1 + acm2 << endl ;
    }

Creating threads using function pointers

您需要带走什么?

  1. std::thread创建一个新线程。 第一个参数是函数指针accumulator_function2的名称。 因此,每个线程将执行此功能。
  2. 传递给std::thread构造函数的其余参数是我们需要传递给accumulator_function2的参数。
  3. 重要说明:除非您将它们包装在std::ref.否则所有传递给accumulator_function2参数都将按值传递std::ref. 这就是为什么我们将vacm1acm2std::ref
  4. std::thread创建的std::thread没有返回值。 如果要返回某些内容,则应将其存储在引用传递的参数之一中,即acm
  5. 每个线程一创建就启动。
  6. 我们使用join()函数等待线程完成

使用函子

您可以使用函子来做完全相同的事情。 以下是使用函子的代码:

class CAccumulatorFunctor3
{
  public :
    void operator () ( const std :: vector < int > &v, 
                    unsigned int beginIndex, unsigned int endIndex)
     {
        _acm = 0 ;
        for ( unsigned int i = beginIndex; i < endIndex; ++i)
        {
            _acm += v[i];
        }
    }
    unsigned long long _acm;
};

Functor Definition

创建线程的代码是:

//Creating Thread using Functor
    {

        CAccumulatorFunctor3 accumulator1 = CAccumulatorFunctor3();
        CAccumulatorFunctor3 accumulator2 = CAccumulatorFunctor3();
        std :: thread t1 ( std ::ref(accumulator1), 
            std ::ref(v), 0 , v.size() / 2 ) ;
        std :: thread t2 ( std ::ref(accumulator2), 
            std ::ref(v), v.size() / 2 , v.size()) ;
        t1.join();
        t2.join();

        std :: cout << "acm1: " << accumulator1._acm << endl ;
        std :: cout << "acm2: " << accumulator2._acm << endl ;
        std :: cout << "accumulator1._acm + accumulator2._acm : " << 
            accumulator1._acm + accumulator2._acm << endl ;
    }

Creating threads using functors

您需要带走什么?

一切都与函数指针非常相似,除了:

  1. 第一个参数是仿函数对象。
  2. 无需传递对函子的引用来存储结果,我们可以将其返回值存储在函子内部的成员变量中,即_acm

使用Lambda函数

作为第三种选择,我们可以在lambda函数中定义每个线程,如下所示:

{unsigned long long acm1 = 0 ;
        unsigned long long acm2 = 0 ;
        std :: thread t1 ([&acm1, &v] {
            for ( unsigned int i = 0 ; i < v.size() / 2 ; ++i)
            {
                acm1 += v[i];
            }
        }) ;
        std :: thread t2 ([&acm2, &v] {
            for ( unsigned int i = v.size() / 2 ; i < v.size(); ++i)
            {
                acm2 += v[i];
            }
        }) ;
        t1.join();
        t2.join();

        std :: cout << "acm1: " << acm1 << endl ;
        std :: cout << "acm2: " << acm2 << endl ;
        std :: cout << "acm1 + acm2: " << acm1 + acm2 << endl ;
    }

Creating threads using lambda functions

同样,所有内容都与函数指针非常相似,除了:

  1. 作为传递参数的替代方法,我们可以使用lambda捕获将引用传递给lambda函数。

任务,期货和承诺

作为std::thread的替代方案,您可以使用任务。

任务的工作与线程非常相似,但是主要区别在于它们可以返回值。 因此,您可以将它们记住为定义线程的更抽象的方式,并在线程返回值时使用它们。

下面是使用任务编写的相同示例:

# include <future>
//Tasks, Future, and Promises
    {
        auto f1 = []( std :: vector < int > &v, 
            unsigned int left, unsigned int right) {
            unsigned long long acm = 0 ;
            for ( unsigned int i = left; i < right; ++i)
            {
                acm += v[i];
            }

            return acm;
        };

        auto t1 = std ::async(f1, std ::ref(v), 
            0 , v.size() / 2 );
        auto t2 = std ::async(f1, std ::ref(v), 
            v.size() / 2 , v.size());

        //You can do other things here!
        unsigned long long acm1 = t1.get();
        unsigned long long acm2 = t2.get();

        std :: cout << "acm1: " << acm1 << endl ;
        std :: cout << "acm2: " << acm2 << endl ;
        std :: cout << "acm1 + acm2: " << acm1 + acm2 << endl ;
    }

您需要带走什么?

  1. 使用std::async定义和创建任务(而不是使用std::thread创建的std::thread
  2. std::async返回的值称为std::future 。 不要被它的名字吓到。 这仅表示t1t2是变量,其值将在将来分配给它们。 我们通过调用t1.get()t2.get()获得它们的值
  3. 如果将来的值尚未准备好,则在调用get()之前主线程将阻塞,直到将来的值准备就绪为止(类似于join() )。
  4. 请注意,我们传递给std::async的函数返回一个值。 该值通过称为std :: promise的类型传递。 同样,不要被它的名字吓到。 在大多数情况下,您不需要了解std::promise细节或定义任何类型为std::promise变量。 C ++库在后台执行此操作。
  5. 默认情况下,每个任务都会在创建后立即开始(有一种更改方法,我不会介绍)。

创建线程的摘要

你有它。 创建线程和我上面解释的一样简单。 您可以使用std::thread

  1. 使用函数指针
  2. 使用函子
  3. 使用lambda函数

或者,您可以使用std::async创建task并在std::future获取返回值。 任务也可以使用函数指针,函子或lambda函数获得。

共享内存和共享资源

简而言之,线程在读取/写入共享内存和资源(例如文件)时应格外小心,以避免出现竞争状况。

C ++ 14提供了几种同步线程的构造,以避免此类竞争情况。

使用Mutex,lock,()和unlock()(不推荐)

以下代码显示了我们如何创建一个关键部分,以便每个线程专有地访问std::cout

std ::mutex g_display_mutex;
thread_function()
{

    g_display_mutex.lock();
    std ::thread::id this_id = std ::this_thread::get_id();
    std :: cout << "My thread id is: " << this_id  << endl ;
    g_display_mutex.unlock();
}

您需要带走什么?

  1. 创建互斥量std::mutex
  2. 使用lock()创建一个关键部分(即保证每次只能由一个线程运行lock()
  3. 关键部分在调用unlock()结束
  4. 每个线程都在lock()处等待,并且只有在该部分中没有其他线程时才进入关键部分。

尽管上述方法有效,但不建议这样做,因为:

  1. 这不是异常安全的方法:如果锁之前的代码生成异常,则将不会执行unlock() ,并且我们绝不会释放可能导致死锁的互斥量
  2. 我们总是要小心,不要忘记调用unlock()

使用std :: lock_guard(推荐)

不要被它的名字lock_guard吓到。 这只是创建关键部分的一种更抽象的方式。

以下是使用lock_guard的同一关键部分:

std ::mutex g_display_mutex;
thread_function()
{
    std ::lock_guard< std ::mutex> guard(g_display_mutex);
    std ::thread::id this_id = std ::this_thread::get_id();
    std :: cout << "From thread " << this_id  << endl ;
}

critical section using lock_guard

您需要带走什么?

  1. 创建std :: lock_guard之后的代码将自动锁定。 不需要显式的lock()unlock()函数调用。
  2. std::lock_guard超出范围时,关键部分自动结束。 这使得异常安全,并且我们也不需要记住调用unlock()
  3. lock_guard仍然需要在其构造函数中使用类型为std::mutex的变量。

我们应该创建多少个线程?

您可以根据需要创建任意数量的线程,但是如果活动线程的数量大于可用CPU核心的数量,则可能毫无意义。

为了获得最大数量的内核,您可以调用: std::thread::hardware_cuncurrency() ,如下所示:

{unsigned int c = std ::thread::hardware_concurrency();
    std :: cout << " number of cores: " << c << endl ;;
}

我没有涵盖的内容

我介绍了创建线程所需的大部分内容。 还有其他一些不太常见的细节,我在这里不包括,但是您可以自己研究它们:

  1. std :: move
  2. std :: promise的详细信息
  3. std :: packaged_task
  4. 条件变量

希望这可以帮助您快速学习C ++多线程。

如果您喜欢这篇文章,请单击拍手并给我反馈。

翻译自: https://hackernoon.com/learn-c-multi-threading-in-5-minutes-8b881c92941f

多线程学习 c

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值