2021-01-23

小伟的C+日常

C++与线程之间擦出的火花(一)

我的能力让我自己有自知之明,我的文章可能有很多不是我自己的东西,还有些可能就是对于老师告诉我的知识的一些誊抄,我的日的是为了可以温故,而我自己对于这个方面的理解,可能不是很多,我只能说尽我所能。首先,我们聊起线程,肯定要先理解线程是个什么东西,这段是我在写这篇博文之前觉得自己可能不能很好的解释线程这个东西,所以,我选择了度娘,以下是我在知乎上照搬过来的,我认为很形象也很容易理解。

作者:pansz
链接:https://www.zhihu.com/question/19901763/answer/13299543
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
1、单进程单线程:一个人在一个桌子上吃菜。2、单进程多线程:多个人在同一个桌子上一起吃菜。3、多进程单线程:多个人每个人在自己的桌子上吃菜。
多线程的问题是多个人同时吃一道菜的时候容易发生争抢,例如两个人同时夹一个菜,一个人刚伸出筷子,结果伸到的时候已经被夹走菜了。此时就必须等一个人夹一口之后,在还给另外一个人夹菜,也就是说资源共享就会发生冲突争抢。
1、对于 Windows 系统来说,【开桌子】的开销很大,因此 Windows 鼓励大家在一个桌子上吃菜。因此 Windows 多线程学习重点是要大量面对资源争抢与同步方面的问题。
2、对于 Linux 系统来说,【开桌子】的开销很小,因此 Linux 鼓励大家尽量每个人都开自己的桌子吃菜。这带来新的问题是:坐在两张不同的桌子上,说话不方便。因此,Linux 下的学习重点大家要学习进程间通讯的方法。
补充:有人对这个开桌子的开销很有兴趣。我把这个问题推广说开一下。开桌子的意思是指创建进程。开销这里主要指的是时间开销。可以做个实验:创建一个进程,在进程中往内存写若干数据,然后读出该数据,然后退出。此过程重复 1000 次,相当于创建/销毁进程 1000 次。
在我机器上的测试结果是: UbuntuLinux:耗时 0.8 秒 Windows7:耗时 79.8 秒 两者开销大约相差一百倍。在 CPU 为多核的情况下,多线程在性能上不如多进程。因而,当前面向多核的服务器端编程中,需要习惯多进程而非多线程。

可能存在过多的赘述,因为我在粘贴的时候是计划删除精炼一些的,可是精炼到最后还是剩下了这么多,接下来到我们的主题,C++中的多线程。
构造函数:

#include <iostream>
#include <thread>

void f1(int n)
{
    for (int i = 0; i < 5; ++i) {
        std::cout << "Thread 1 executing\n";
        ++n;
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
}

void f2(int& n)
{
    for (int i = 0; i < 5; ++i) {
        std::cout << "Thread 2 executing\n";
        ++n;
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
}

class foo
{
public:
    void bar()
    {
        for (int i = 0; i < 5; ++i) {
            std::cout << "Thread 3 executing\n";
            ++n;
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
    }
    int n = 0;
};

class baz
{
public:
    void operator()()
    {
        for (int i = 0; i < 5; ++i) {
            std::cout << "Thread 4 executing\n";
            ++n;
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
    }
    int n = 0;
};

int main()
{
    int n = 0;
    foo f;
    baz b;
    std::thread t1; // t1 不是线程
    std::thread t2(f1, n + 1); // 按值传递
    std::thread t3(f2, std::ref(n)); // 按引用传递
    std::thread t4(std::move(t3)); // t4 现在运行 f2() 。 t3 不再是线程
    std::thread t5(&foo::bar, &f); // t5 在对象 f 上运行 foo::bar()
    std::thread t6(std::ref(b)); // t6 在对象 b 上运行 baz::operator()
    t2.join();
    t4.join();
    t5.join();
    t6.join();
    std::cout << "Final value of n is " << n << '\n';
    std::cout << "Final value of foo::n is " << f.n << '\n';
    std::cout << "Final value of baz::n is " << b.n << '\n';
}

上面的代码是我在帮助手册上粘过来的,当然,自己也理解了一下,首先,对于t1的构造是失败的,在我的理解来看,可以舒说是构造了一个空的线程,没有给这个线程分配它该干的事,所以这个线程是无效的;而对于t2的构造,因为f1()函数是带参的,所以创建这个线程的时候得将值传递进去,即std::thread t2(函数名,参数值),当然函数的参数有几个,括号里就得有几个传递的值;t3的创建来说,f2函数的参数是对于n的引用,所以线程的创建应该对应传递n的引用,这个时候就要使用std::ref()这个特定的函数,而不是用“&”。t4的创建,是使用了std::move()函数,这个函数是将t3的资源全部转移到了t4的线程中,这个时候t3在我的理解来说就变成了一个空线程,和t1本质上没有区别,那么最后在程序需要结束的时候就不用在等待这个线程的结束;t5的话,首先bar()函数是在foo对象中,所以得引用对象的对应方法,后面再传递创建的具体对象f的地址;t6,是对于()的重载而创建的线程,说实话我是略微有点看不懂的,我只能说让自己记住还有这种的用法:括号里直接用std::ref()函数将b传递进去。
在这里,我们还可以看到,对于每一个线程,在函数要结束时都需要用创建的线程对象的join()方法等待线程的结束。
join函数: 阻塞当前线程直至join函数对应的线程结束,即在每一次的程序结束前都得对于每一个真实的线程都要使用一次这个函数,这个地方我用了一下“真实”这个词,在我看来就是可以运行的就是真实的,而最大的反面例子就是上面讲到的t3,它的资源已经被转移给了t4线程,所以它就没有必要再使用join函数。
id: 作为线程函数的唯一成员变量,也是线程的唯一标识,我们可以通过线程对象的get_id()方法来获取线程的id。
joinable函数: 检查线程是否活跃,帮助手册上面的解释有点难懂,在我的理解来看,就是拥有资源正常运行的线程,使用这个函数就会返回“true”,否则返回的就是“false”。
detach函数: 这个函数的解释,我读着有点难懂,使用这个函数就会将此线程分离出来,独立的执行,一旦该线程退出就释放所有资源,但在我看来最直接的影响是,使用了这个函数以后,就没有看见此线程的join函数使用,以我现在的知识感觉无法解释这个问题。
swap函数: 帮助手册上说是交换两个线程的底层柄,说实话,我没听懂,我看完帮助手册最真实的感受是交换了两个线程id,按我的理解来说,交换了id,应该也就是交换了两个线程的资源,我暂且这么理解,因为我试着求证,结果无功而返,具体的使用,有两种使用方法:1、std::swap(t1,t2);2、t1.swap(t2);
好友召唤王者峡谷,明晚继续。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值