前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【C++11】 让多线程开发变得简单--线程

【C++11】 让多线程开发变得简单--线程

作者头像
CPP开发前沿
发布2021-11-16 14:11:38
4760
发布2021-11-16 14:11:38
举报
文章被收录于专栏:CPP开发前沿CPP开发前沿

C++ 11之前,C++语言并没有提供支持,想要开发多线程程序就要借助于操作系统提供的多线程接口,但是,这样并不能开发跨平台可移植的并发程序,C++11提供了多线程语言支撑,使得程序的可移植性大大提升。

1 线程

线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。具备占用并发、占用资源少、资源共享等优势。同样,在使用线程进行编码时也要关注多线程的一些缺点,如:变量共享导致的结果差异、多线程调试、死锁等很多现实的问题,因此在使用多线程编码时要格外注意。

1.1 创建线程

C++ 11中创建一个线程是很简单的事情,只需要使用std::thread就可以轻松创建一个线程,我们要做的只是提供一个线程函数或者函数对象,创建线程时也可以同时给线程函数指定参数,代码如下:

代码语言:javascript
复制
void foo() 
{
  std::cout<<"foo"<<std::endl;
}

void bar(int x)
{
   std::cout<<"bar::x"<<x<<std::endl;
}

int main() 
{
  std::thread first (foo); 
  std::thread second (bar,0);
  std::cout << "main, foo and bar now execute concurrently...\n";
  first.join();
  second.join();
  std::cout << "foo and bar completed.\n";
  return 0;
}

上面的代码创建了两个线程,执行的线程函数一个有参一个无参,线程运行后输出的结果是这样的:

代码语言:javascript
复制
main, foo and bar now execute concurrently...
foo
bar::x=0
foo and bar completed.

但是也可能有其他输出形式,原因先不做解释,后面会揭晓答案。在这个代码中,创建的线程对象分别调用了join方法,这个方法的作用是阻塞线程,直到线程函数执行完毕,如果线程函数有返回值,返回值将会被忽略。

在thread中除了join外也提供了另外一个方法:detach,线程创建完成后,调用detach方法,线程就会和主线程进行分离,编程一个后台的线程去执行,主线程也不会被阻塞。需要关注的是一旦分离,两个线程之间就不会再有关联,也不能在通过join等待线程函数执行完毕。使用方法如下:

代码语言:javascript
复制
void foo() 
{
    while(1)
    {
        std::cout<<"foo"<<std::endl;
        Sleep(1000);
    }
  
}

int main() 
{
  std::thread first (foo); 
  while(1)
  {
     std::cout << "main thread.\n";
      Sleep(1000);
  }
  first.detach();
  return 0;
}

上面的代码线程创建完毕后,主线程输出“main thread”。线程first就会和主线程脱离,在后台执行线程函数,相互交叉打印日志。

按照上面的方法创建线程是一件非常简单的事情,但是,也有弊端,既:std::thread如果在线程函数返回前被析构就会发生意想不到的错误,因此需要确保线程函数在线程被析构之前执行完毕。如:可以将线程保存到一个容器中。如:

代码语言:javascript
复制
void foo() 
{
  std::cout<<"foo"<<std::endl;
}
void bar(int x)
{
   std::cout<<"bar::x="<<x<<std::endl;
}
std::vector<std::thread> g_list;
std::vector<std::shared_ptr<std::thread>> g_list2;

void CreateThread()
{
    //std::thread t(foo);
   // g_list.push_back(std::move(t));
   g_list2.push_back(std::make_shared<std::thread>(bar,2));
}

int main() 
{
 CreateThread();
 for(auto &thread : g_list)
 {
     thread.join();
 }
 for(auto &thread : g_list2)
 {
     thread->join();
 }
  return 0;
}

1.2 线程基本用法

1) 获取当前信息:获取线程ID和CPU核心数

代码语言:javascript
复制
void foo() 
{
    
}

int main() 
{
  std::thread first (foo); 
  //获取线程ID
  std::cout<<first.get_id()<<std::endl;
  //获取CPU核心数
  std::cout<<std::thread::hardware_concurrency()<<std::endl;
  return 0;
}

2)线程休眠

代码语言:javascript
复制
void foo() 
{
    std::this_thread::sleep_for(std::chrono::seconds(3));
    std::cout<<"time out"<<std::endl;
}

int main() 
{
  std::thread first (foo); 
  first.join();
  return 0;
}

2 互斥量

互斥量是一种线程同步的手段,用来保护多线程同时访问的共享数据,在C++ 11中,提供了多种互斥量,如下:

  • std::mutex: 独占互斥
  • std::timed_mutex:带有超时的互斥量
  • std::recursive_mutex:递归互斥量
  • std::recursive_timed_mutex:待超时的递归互斥量

2.1 独占互斥量

互斥量通常借助lock方法阻塞线程,取得控制权执行完毕后再调用unlock进行释放,在这个过程中,lock和unlock需要成对出现,这种方式时候同步的,同样也有一种方法是异步的,try_lock,取得互斥锁后会返回true,如果没有取得则返回false,是非阻塞的,std::mutex的用法如下:

代码语言:javascript
复制
std::mutex g_lock;
void foo() 
{
    g_lock.lock();
    std::cout<<"entry thread and get lock"<<std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout<<"leave thread and release lock"<<std::endl;
    g_lock.unlock();
}

int main() 
{
  std::thread first (foo); 
  std::thread second (foo);
  std::thread three (foo); 
  first.join();
  second.join();
  three.join();
  return 0;
}

上面的代码通过互斥锁实现了三个线程的资源共享,运行结果如下:

代码语言:javascript
复制
entry thread and get lock
leave thread and release lock
entry thread and get lock
leave thread and release lock
entry thread and get lock
leave thread and release lock

lock和unlock必须成对出现,如果缺少unlock可能会造成第一个线程获得锁资源后,不进行释放,导致后面的线程产生所等待,导致线程假死。为了防止这种现像产生,可以使用lock_guard进行简化,他会在构造时获得锁资源,超出生命周期后对锁资源进行释放。如:

代码语言:javascript
复制
std::mutex g_lock;
void foo() 
{
    std::lock_guard<std::mutex> locker(g_lock);
    std::cout<<"entry thread and get lock"<<std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout<<"leave thread and release lock"<<std::endl;
}

2.2 递归的独占互斥变量

递归锁一般不推荐使用,主要原因如下:

  • 递归锁的使用会让逻辑变得复杂,导致多线程同步产生更加晦涩难懂的问题;
  • 递归锁的效率同非递归锁相比,效率低;
  • 递归锁允许同时获得同一个互斥量,超过一定次数后再次调用会产生系统异常

因此,鉴于以上原因,实际编码过程中并不建议使用递归锁,这里为了演示递归锁的使用方法,举例代码如下:

代码语言:javascript
复制
struct Complex{
    std::recursive_mutex mutex;
    int i;
    Complex():i(2){};
    void mul(int x)
{
        std::lock_guard<std::recursive_mutex> lock(mutex);
        i *= x;
        std::cout<<"mul::i="<<i<<std::endl;
    }
    
    void div(int x)
{
        std::lock_guard<std::recursive_mutex> lock(mutex);
        i /= x;
        std::cout<<"div::i="<<i<<std::endl;
    }
    
    void both(int x,int y)
{
        std::lock_guard<std::recursive_mutex> lock(mutex);
        mul(x);
        div(y);
    }    
};

int main() 
{
  Complex complex;
  complex.both(4,8);
  return 0;
}

2.3 带超时的互斥量

带超时的互斥锁主要是在原来互斥锁的基础上增加一个超时等待的功能,这样就不用一直去获取互斥锁,另外如果在等待的时间内还没有获得锁资源,在超时后还可以继续处理其他的事情。

超时互斥锁比普通的互斥锁多了两个接口,分别是:try_lock_for和try_lock_until,这两个接口的功能是设置获取互斥锁的等待超时时间。使用方法如下:

代码语言:javascript
复制
std::timed_mutex mtx;

void fireworks (int pid) {
  // waiting to get a lock: each thread prints "-" every 200ms:
  while (!mtx.try_lock_for(std::chrono::milliseconds(200))) {
    std::cout << "-";
  }
  // got a lock! - wait for 1s, then this thread prints "*"
  std::this_thread::sleep_for(std::chrono::milliseconds(1000));
  std::cout << pid <<"*\n";
  mtx.unlock();
}

int main ()
{
  std::thread threads[10];
  // spawn 10 threads:
  for (int i=0; i<10; ++i)
    threads[i] = std::thread(fireworks,i);

  for (auto& th : threads) th.join();

  return 0;
}

上面的代码通过while循环不断获取超时锁,如果达到超时时间还没有获取到锁就输出一个-,如果获取到锁则休眠1000毫秒输出*和线程号。代码运行结果如下:

代码语言:javascript
复制
------------------------------------0*
----------------------------------------3*
-----------------------------------9*
------------------------------8*
-------------------------6*
--------------------1*
---------------4*
----------7*
-----5*
2*
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-10-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 CPP开发前沿 微信公众号,前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档