Linux下多线程编程

2018-07-12  本文已影响19人  XDgbh
image.png
image.png
image.png
image.png
image.png

线程管理

image.png
image.png
image.png
image.png

实例1

#include <pthread.h>
#include <iostream>
void *  PrintAs( void * unused )
{
  while( true )    std::cerr << 'a';
  return NULL;
}
void *  PrintZs( void * unused )
{
  while( true )    std::cerr << 'z';  
//用cerr可以立即输出到屏幕而不用像cout那样先缓存再输出
  return NULL;
}
int  main()
{
  pthread_t  thread_id;  //保存线程id的变量
  //子线程创建,四个参数。之后PrintAs函数作为一个子线程单独运行
  pthread_create( &thread_id, NULL, &PrintAs, NULL );
  //主线程中也有函数在运行。这个函数会阻止主线程结束,
  //因此子线程也还可以继续运行,否则主线程退出子线程也得结束
  PrintZs( NULL );
  return 0;
}

实例2

#include <pthread.h>
#include <iostream>

class InfoPrinted
{
public:
  InfoPrinted( char c, int n ) : _c(c), _n(n)  {  }
  void  Show() const  {  for( int i = 0; i < _n; i++ )  std::cerr << _c;  }
private:
  char _c;
  int _n;
};

void *  PrintInfo( void * info )
{
  InfoPrinted *  p = reinterpret_cast<InfoPrinted *>( info );
  if( p )    p->Show();
  return NULL;
}
//  注意:本程序大部分情况下不会输出任何结果
int  main()
{
  pthread_t  tid1, tid2;
  //  构造InfoPrinted类的动态对象,作为线程函数参数传递给线程tid1
  //  输出100个‘a’
  InfoPrinted *  p = new InfoPrinted( 'a', 100 );
  pthread_create( &tid1, NULL, &PrintInfo, reinterpret_cast<void *>( p ) );
  //  构造InfoPrinted类的动态对象,作为线程函数参数传递给线程tid2
  //  输出100个‘z’
  InfoPrinted *  q = new InfoPrinted( 'z', 100 );
  pthread_create( &tid2, NULL, &PrintInfo, reinterpret_cast<void *>( q ) );
  //  使用本注释行替换上述线程,可以看到输出结果,可能仅有部分输出
  //  PrintInfo( reinterpret_cast<void *>( q ) );
  return 0;
}

上面例子出错分析:


image.png
image.png

pthread_join(tid1, NULL);会首先阻塞主线程,等待被监控的子线程1结束后,主线程才会继续往下执行,然后才会执行到pthread_join(tid2, NULL);这一句,会再阻塞等待子线程2结束后,主线程才能继续往下执行。

实例3——线程函数返回值

#include <pthread.h>
#include <cmath>
#include <iostream>

void *  IsPrime( void * n )
{
  unsigned int  p = reinterpret_cast<unsigned int>( n );
  unsigned int  i = 3u, t = (unsigned int)sqrt( p ) + 1u;
  if( p == 2u )
    return reinterpret_cast<void *>( true );
  if( p % 2u == 0u )
    return reinterpret_cast<void *>( false );
  while( i <= t )
  {
    if( p % i == 0u )
      return reinterpret_cast<void *>( false );
    i += 2u;
  }
  return reinterpret_cast<void *>( true );
}
//  使用g++ main.cpp –pthread –lm –fpermissive编译
//  以防止编译器将void*到int的转型当作错误
int  main()
{
  pthread_t  tids[8];
  bool  primalities[8];
  int i;
  for( i = 0; i < 8; i++ )
    pthread_create( &tids[i], NULL, &IsPrime, reinterpret_cast<void *>( i+2 ) );
  for( i = 0; i < 8; i++ )
    pthread_join( tids[i], reinterpret_cast<void **>( &primalities[i] ) );
  for( i = 0; i < 8; i++ )
    std::cout << primalities[i] << " ";
  std::cout << std::endl;
  return 0;
}
image.png
image.png
image.png
image.png
image.png

实例4

#include <pthread.h>
//  线程函数
void *  ThreadFunc( void * arg )  {  ...  }
int  main()
{
  pthread_attr_t  attr;
  pthread_t  thread;
  //  初始化线程属性
  pthread_attr_init( &attr );
  //  设置线程属性的分离状态
  pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_DETACHED );
  //  创建线程
  pthread_create( &thread, &attr, &ThreadFunc, NULL );
  //  清除线程属性对象
  pthread_attr_destroy( &attr );
  //  无需联结该线程
  return 0;
}
image.png
image.png
image.png
image.png

用线程撤销状态构建临界区

//  账户转账,在同一个数组accounts中from和to下标的两个数进行转账
void Transfer( double * accounts, int from, int to, double amount )
{
  int ocs;
  //  数据有效性检查代码在此,确保转账操作合法有效

  //  将线程设置为不可撤销的,进入临界区
  pthread_setcancelstate( PTHREAD_CANCEL_DISABLE, &ocs );
  //临界区内的代码要么必须全部执行完,要么一条都不执行,以保证转账正确性
  accounts[to] += amount;
  accounts[from] -= amount;

  //  恢复线程的撤销状态,离开临界区
  pthread_setcancelstate( ocs, NULL );
}
image.png

实例:让每个线程都有自己独有的log日志文件

#include <pthread.h>
#include <stdio.h>
static pthread_key_t  tlk;    //  关联线程日志文件指针的键
void  WriteToThreadLog( const char * msg )
{
  FILE *  fp = ( FILE * )pthread_getspecific( tlk );
  fprintf( fp, "%d: %s\n", (int)pthread_self(), msg );
}
void  CloseThreadLog( void * fp )
{
  fclose( ( FILE * )fp );
}
void *  ThreadFunc( void * args )
{
  char  filename[255];
  FILE *  fp;
  //  生成与线程ID配套的日志文件名
  sprintf( filename, "thread%d.log", (int)pthread_self() );
  fp = fopen( filename, "w" );
  //  设置线程日志文件指针与键的局部存储关联
  pthread_setspecific( tlk, fp );
  //  向日志中写入数据,不同的线程会写入不同的文件
  WriteToThreadLog( "Thread starting..." );
  return NULL;
}
int  main()
{
  int  i;
  pthread_t  threads[8];
  //  创建键,使用CloseThreadLog()函数作为其清除程序
  pthread_key_create( &tlk, CloseThreadLog );
  for( i = 0; i < 8; ++i )
    pthread_create( &threads[i], NULL, ThreadFunc, NULL );
  for( i = 0; i < 8; ++i )
    pthread_join( threads[i], NULL );
  pthread_key_delete( tlk );
  return 0;
}

最重要的是 :
// 设置线程日志文件指针与键的局部存储【互相关联】起来
pthread_setspecific( tlk, fp );

image.png

线程清除实例

#include <malloc.h>
#include <pthread.h>
void *  AllocateBuffer( size_t size )
{
  return malloc( size );
}
void  DeallocateBuffer( void * buffer )
{
  free( buffer );
}
void  DoSomeWork()
{
  void *  temp_buffer = AllocateBuffer( 1024 );
  //  注册清除处理函数
  pthread_cleanup_push( DeallocateBuffer, temp_buffer );
  //  此处可以调用pthread_exit()退出线程或者撤销线程
  //  取消注册,传递非0值,实施清除任务
  pthread_cleanup_pop( 1 );
}
image.png

C++中自定义异常类来引发线程资源释放问题

#include <pthread.h>
class EThreadExit  {
public:
  EThreadExit( void * ret_val ) : _thread_ret_val(ret_val)  {  }
  //  实际退出线程,使用对象构造时的返回值
  void* DoThreadExit ()  {  pthread_exit( _thread_ret_val );  }
private:
  void *  _thread_ret_val;
};
void *  ThreadFunc( void * arg )
{
  try  {
    if( 线程需要立即退出 )
      throw EThreadExit(  线程返回值 );  //1引发异常
  }
  catch( const EThreadExit & e )  {    //2捕捉到这个异常,调用线程释放资源再退出的函数
    e.DoThreadExit();    //  执行线程实际退出动作
  }
  return NULL;
}

线程同步机制

image.png
image.png
//  多线程有问题的程序代码。单线程情况下没问题
#include <list>
struct Job;
std::list<Job *>  job_queue;
//  线程函数
void *  DequeueJob( void * arg )
{
  if( !job_queue.empty() )
  {
    Job *  job = job_queue.front();
    job_queue.pop_front();
    ProcessJob( job );
    delete job,  job = NULL;
  }
  return NULL;
} 
image.png
image.png
image.png
image.png
image.png

多线程处理队列任务实例

//  完整程序代码
#include <pthread.h>
#include <iostream>
#include <list>
struct Job  {
  Job( int x = 0, int y = 0) : x(x), y(y)  {  }
  int x, y;
};
//  一般要求临界区代码越短越好,执行时间越短越好,使用C++ STL可能并不是好选择
std::list<Job *>   job_queue;
pthread_mutex_t   job_queue_mutex = PTHREAD_MUTEX_INITIALIZER;
//  此处作业处理工作仅为示例,简单输出线程ID和作业内容信息
void  ProcessJob( Job * job )
{
  std::cout << "Thread " << (int)pthread_self();
  std::cout << " processing (" << job->x << ", " << job->y << ")\n";
}
//  处理作业时需要加锁
void *  DequeueJob( void * arg )
{
  while( true )  {
    Job *  job = NULL;
    pthread_mutex_lock( &job_queue_mutex );
    if( !job_queue.empty() )    {
      job = job_queue.front();  //  获取表头元素
      job_queue.pop_front();    //  删除表头元素
    }
    pthread_mutex_unlock( &job_queue_mutex );
    if( !job )    break;
    ProcessJob( job );
    delete job,  job = NULL;
  }
  return NULL;
}
//  作业入队时需要加锁
void *  EnqueueJob( void * arg )
{
  Job * job = reinterpret_cast< Job * >( arg );
  pthread_mutex_lock( &job_queue_mutex );    //  锁定互斥
  job_queue.push_back( job );

  //  入队时也输出线程ID和作业内容信息
  std::cout << "Thread " << (int)pthread_self();
  std::cout << " enqueueing (" << job->x << ", " << job->y << ")\n";

  pthread_mutex_unlock( &job_queue_mutex );    //  解锁
  return NULL;
}
int  main()
{
  int  i;
  pthread_t  threads[8];
  for( i = 0; i < 5; ++i )
  {
    Job *  job = new Job( i+1, (i+1)*2 );
    pthread_create( &threads[i], NULL, EnqueueJob, job );
  }
  for( i = 5; i < 8; ++i )
    pthread_create( &threads[i], NULL, DequeueJob, NULL );
  for( i = 0; i < 8; ++i )
    pthread_join( threads[i], NULL );
  return 0;
}
image.png

信号量semaphore

image.png

typedef unsigned long int pthread_t;
来自 /usr/include/bits/pthreadtypes.h
用途:pthread_t用于声明线程ID。

typedef union
{
  char __size[__SIZEOF_SEM_T];  //__SIZEOF_SEM_T宏定义为16或32
  long int __align;
} sem_t;

来自 /usr/include/bits/semaphore.h
sem_t是一个联合体,用于定义信号量。

信号量的PV操作来源:
火车运行控制系统中的“信号灯”(semaphore,或叫“信号量”)概念,中国读者常常不明白这一同步机制为什么叫PV操作,原 来这是狄克斯特拉用荷兰文定义的,因为在荷 兰文中,通过叫Passeren,释放叫Vrijgeven,(火车通过-释放)PV操作因此得名。

所谓信号量,实际上就是用来控制进程或线程状态的一个代表某一资源的数目。例如,P1和P2是分别将数据送入缓冲区B和从缓冲区B读出数据的两个进程,为了防止这两个进程并发时产生错误,狄克斯特拉设计了一种同步机制叫“PV操作”,P操作和V操作是执行时不被打断的两个操作系统原语【原子操作加一或减一】。执行P操作P(S)时信号量S的值减1,若结果不为负则P(S)执行完毕,否则执行P操作的进程阻塞暂停以等待释放。 执行V操作V(S)时,S的值加1,若结果小于0说明有进程被阻塞则释放一个因执行P(S)而阻塞等待的进程。
例如,P1和P2是分别将数据送入缓冲区B和从缓冲区B读出数据的两个进程,为了防止这两个进程并发时产生错误,对P1和P2可定义两个信号量S1和S2,初值分别为1和0。进程P1在向缓冲B送入数据前执行P操作P(S1)【S1减1】,在送入数据后执行V操作V(S2)【S2加1】。进程P2在从缓冲B读取数据前先执行P操作P(S2)【S2减1】,在读出数据 后执行V操作V(S1)【S1加1】。当P1往缓冲B送入一数据后信号量S1之值变为0,在该数据读出后S1之值才又变为1,因此在前一数未读出前后一数不会送入,从而保证了P1和P2之间的同步。
这是对只有一个缓冲区资源读写的信号量同步,才需要两个信号量实现。

image.png

用一个信号量实现任务队列的互斥等待和同步

//  完整程序代码
#include <pthread.h>
#include <iostream>
#include <list>
struct Job  {
  Job( int x = 0, int y = 0) : x(x), y(y)  {  }
  int x, y;
};
std::list<Job *>   job_queue;
pthread_mutex_t   job_queue_mutex = PTHREAD_MUTEX_INITIALIZER;

//  控制作业数目的信号量===========此处信号量相当于队列中任务数量,
//任务增加一个时信号量加一(V操作),做完一个任务时信号量减一(P操作)
//队列中没有任务时(信号量等于0),会阻塞等待新任务的到来(等待信号量变为1)
sem_t   job_queue_count;

void  ProcessJob( Job * job )
{
  std::cout << "Thread " << (int)pthread_self();
  std::cout << " processing (" << job->x << ", " << job->y << ")\n";
}
//  处理作业时需要加锁
void *  DequeueJob( void * arg )
{
  while( true )  {
    Job *  job = NULL;
    sem_wait( &job_queue_count );   //  等待作业队列中有新作业================
    pthread_mutex_lock( &job_queue_mutex );
    if( !job_queue.empty() )  {
      job = job_queue.front();  //  获取表头元素
      job_queue.pop_front();        //  删除表头元素
    }
    pthread_mutex_unlock( &job_queue_mutex );
    if( !job )    break;
    ProcessJob( job );
    delete job,  job = NULL;
  }
  return NULL;
}
//  作业入队时需要加锁
void *  EnqueueJob( void * arg )
{
  Job *  job = reinterpret_cast< Job * >( arg );
  pthread_mutex_lock( &job_queue_mutex );    //  锁定互斥
  job_queue.push_back( job );

  //  新作业入队,递增信号量==================================
  sem_post( &job_queue_count );

  //  入队时也输出线程ID和作业内容信息
  std::cout << "Thread " << (int)pthread_self();
  std::cout << " enqueueing (" << job->x << ", " << job->y << ")\n";

  pthread_mutex_unlock( &job_queue_mutex );    //  解锁
  return NULL;
}
int  main()
{
  int  i;
  pthread_t  threads[8];
  if( !job_queue.empty() )    job_queue.clear();
  sem_init( &job_queue_count, 0, 0 );   //  初始化,非进程共享,初始值0
  for( i = 0; i < 5; ++i )
  {
    Job *  p = new Job( i+1, (i+1)*2 );
    pthread_create( &threads[i], NULL, EnqueueJob, p );
  }
  for( i = 5; i < 8; ++i )
    pthread_create( &threads[i], NULL, DequeueJob, NULL );
  for( i = 0; i < 8; ++i )
    pthread_join( threads[i], NULL );       //  等待线程终止,无作业时线程被阻塞
  sem_destroy( &job_queue_count );  //  销毁作业信号量
  return 0;
}

【进程间通信之-信号量semaphore--linux内核剖析(十) - CSDN博客】 https://blog.csdn.net/gatieme/article/details/50994533

条件变量

image.png
image.png

C++11线程库,平台无关,可通用

image.png
image.png

(1) std::this_thread::yield(); 是将当前线程所抢到的CPU”时间片A”让渡给其他线程(其他线程会争抢”时间片A”,
注意: 此时”当前线程”不参与争抢).
等到其他线程使用完”时间片A”后, 再由操作系统调度, 当前线程再和其他线程一起开始抢CPU时间片.
(2) 如果将 std::this_thread::yield();上述语句修改为: return; ,则将未使用完的CPU”时间片A”还给操作系统, 再由操作系统调度, 当前线程和其他线程一起开始抢CPU时间片.

image.png
image.png

线程类实例

//  无参数线程函数
#include <iostream>
#include <thread>

void  ThreadFunc()
{
  std::cout << "Thread ID: " << std::this_thread::get_id() << std::endl;
}

int  main()
{
  std::thread  t( &ThreadFunc );        //  创建线程对象并运行
  t.join();             //  等待线程结束
  return 0;
}
//  带双参数的线程函数
#include <iostream>
#include <thread>
void  ThreadFunc( int a, int b )
{
  std::cout << "Thread ID: " << std::this_thread::get_id() << std::endl;
  std::cout << a << " + " << b << " = " << a + b << std::endl;
}
int main()
{
  int  m = 10, n = 20;
  //  C++11标准库使用可变参数的模板形式参数列表,线程函数参数个数任意
  std::thread  t( &ThreadFunc, m, n );
  t.join();
  return 0;
}
//  带双参数的函子对象
#include <iostream>
#include <thread>
class Functor  {
public:
  void  operator()( int a, int b )  {
    std::cout << "Thread ID: " << std::this_thread::get_id() << std::endl;
    std::cout << a << " + " << b << " = " << a + b << std::endl;
  }
};
int main()
{
  int  m = 10, n = 20;
  std::thread  t( Functor(), m, n );
  t.join();
  return 0;
}
//  使用std::bind()函数绑定对象及其普通成员函数
#include <iostream>
#include <thread>
class Worker  {
public:
  Worker( int a = 0, int b = 0 ) : _a(a), _b(b)  {  }
  void  ThreadFunc()  {  ……  }
private:  int  _a, _b;
};

int  main()
{
  Worker  worker( 10, 20 );
  std::thread  t( std::bind( &Worker::ThreadFunc, &worker ) );
  t.join();
  return 0;
}
image.png
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
std::mutex  x;
void  ThreadFunc()
{
  x.lock();
  std::cout << std::this_thread::get_id() << " is entering..." << std::endl;
  std::this_thread::sleep_for( std::chrono::seconds( 3 ) );
  std::cout << std::this_thread::get_id() << " is leaving..." << std::endl;
  x.unlock();
}
int  main()
{
  std::vector<std::thread *>  v( 8 );
  for( int i = 0; i < 8; i++ )    v[i] = new std::thread( ThreadFunc );
  for( int i = 0; i < 8; i++ )    v[i]->join();
}
image.png
image.png
image.png
image.png
std::mutex mut; //全局的互斥锁
//  使用互斥管理策略类重新实现线程函数
template< typename T > 
class Worker
{
public:
  explicit Worker( int no, T a = 0, T b = 0 ) : _no(no), _a(a), _b(b)  {  }
  void  ThreadFunc( T * r )
  {
    {    // 使用复合语句块封装临界区操作,块结束时即释放局部对象
      std::lock_guard<std::mutex>  locker( mut );    //  构造对象的同时加锁
      *r = _a + _b;
    }    //  无需手工解锁,locker对象在析构时自动解锁
    std::cout << "Thread No: " << _no << std::endl;
    std::cout << _a << " + " << _b << " = " << _a + _b << std::endl;
  }
private:
  int  _no;
  T  _a, _b;
};

实例

//  转账处理示例
#include <iostream>
#include <mutex>
#include <thread>

class Account
{
public:
  explicit Account( double balance ) : _balance(balance)  {  }
  double  GetBalance()  {  return _balance; }
  void  Increase( double amount )  {  _balance += amount;  }
  void  Decrease( double amount )  {  _balance -= amount;  }
  std::mutex &  GetMutex()  {  return _x; }
private:
  double  _balance;
  std::mutex  _x;
};
//  避免死锁,使用std::lock()函数锁定多个互斥,不同的锁定顺序不会导致死锁
//  加锁时有可能引发异常,std::lock()函数会处理该异常
//  将解锁此前已加锁的部分互斥,然后重新引发该异常
void Transfer( Account & from, Account & to, double amount )
{
  std::unique_lock<std::mutex>  locker1( from.GetMutex(), std::adopt_lock );
  std::unique_lock<std::mutex>  locker2( to.GetMutex(), std::adopt_lock );
  std::lock( from.GetMutex(), to.GetMutex() );
  from.Decrease( amount );    to.Increase( amount );
}
int main()
{
  Account  a1( 100.0 ), a2( 200.0 );
  //  线程参数采用值传递机制,如果要传递引用,调用std::ref()函数
  std::thread  t1( Transfer, std::ref( a1 ), std::ref( a2 ), 10.0 );
  std::thread  t2( Transfer, std::ref( a2 ), std::ref( a1 ), 20.0 );
  t1.join();    t2.join();
  return 0;
}
image.png

https://www.cnblogs.com/haippy/p/3252041.html
C++11 并发指南五(std::condition_variable 详解)

image.png
image.png
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex  x;
std::condition_variable  cond;
bool  ready = false;
bool  IsReady()  {  return ready;  }
void  Run( int no )
{
  std::unique_lock<std::mutex>  locker( x );
  while( !ready )       //  若标志位非true,阻塞当前线程
    cond.wait( locker );    //  解锁并睡眠,被唤醒后重新加锁
  //  以上两行代码等价于cond.wait( locker, &IsReady );
  //  第二个参数为谓词,亦可使用函子实现
  std::cout << "thread " << no << '\n';
}
int  main()
{
  std::thread  threads[8];
  for( int i = 0; i < 8; ++i )
    threads[i] = std::thread( Run, i );
  std::cout << "8 threads ready...\n";
  {
    std::unique_lock<std::mutex>  locker( x );    //  互斥加锁
    ready = true;       //  设置全局标志位为true
    cond.notify_all();  //  唤醒所有线程
  }    //  离开作用域,自动解锁;可将此复合语句块实现为函数
  //  基于区间的循环结构,对属于threads数组的所有元素t,执行循环体
  for( auto & t: threads )
    t.join();
  return 0;
}
image.png
image.png
#include <atomic>
#include <iostream>
#include <thread>
int  n = 0;
std::atomic<int>  a(0);
void  AddAtomically(int m)  { while (m--)  a.fetch_add(1); }
void  Add(int m)  { while (m--)  ++n; }
int  main()
{
    std::thread  ts1[8], ts2[8];
    
    //注意这里你是简单的用=赋值,而是用了函数move移动语义。因为 thread 不可被拷贝构造
    //线程的所有权转移,std::thread是不可复制的,但是是可移动的movable,
    //也就是可以把线程的所有权转移,但一个线程只能有一个所有权。
    //std::thread t2(std::move(t1));=========或者std::thread t3 = std::move(t2);
    for (auto & t : ts1)  t = std::move(std::thread(AddAtomically, 1000000));
    for (auto & t : ts2)  t = std::move(std::thread(Add, 1000000));
    for (auto & t : ts1)  t.join();
    for (auto & t : ts2)  t.join();
    //  输出结果:a值固定,而n值多次运行结果可能不同
    std::cout << "a = " << a << std::endl;
    std::cout << "n = " << n << std::endl;
    return 0;
}
image.png
image.png

使用指针形式的函数参数返回线程的结果

//  使用指针作为函数参数,获取线程计算结果
#include <iostream>
#include <vector>
#include <tuple>
#include <thread>
#include <mutex>
std::mutex x;
//  劳工线程类模板,处理T型数据对象
template< typename T >  
class Worker
{
public:
    explicit Worker(int no, T a = 0, T b = 0) : _no(no), _a(a), _b(b)  {  }
    void  ThreadFunc(T * r)  { x.lock();    *r = _a + _b;    x.unlock(); }
private:
    int  _no;   //  线程编号,非线程ID
    T  _a, _b;  //  保存在线程中的待处理数据
};
int main()
{
    //  定义能够存储8个三元组的向量v,元组首元素为指向劳工对象的指针
    //  次元素保存该劳工对象计算后的结果数据,尾元素为指向劳工线程对象的指针
    //  向量中的每个元素都表示一个描述线程运行的线程对象,
    //  该线程对象对应的执行具体任务的劳工对象,及该劳工对象运算后的返回值
    std::vector< std::tuple<Worker<int>*, int, std::thread*> >  v(8);

    //  构造三元组向量,三元编号顺次为0、1、2
    for (int i = 0; i < 8; i++)
        v[i] = std::make_tuple(new Worker<int>(i, i + 1, i + 2), 0, nullptr);

    //  输出处理前结果;使用std::get<n>(v[i])获取向量的第i个元组的第n个元素
    //  三元编号顺次为0、1、2,因而1号元保存的将是劳工对象运算后的结果
    for (int i = 0; i < 8; i++)
        std::cout << "No. " << i << ": result = " << std::get<1>(v[i]) << std::endl;
    //  创建8个线程分别计算
    for (int i = 0; i < 8; i++)
    {
        //  将劳工类成员函数绑定为线程函数,对应劳工对象绑定为执行对象
        //  将构造线程对象时传递的附加参数作为被绑定的线程函数的第一个参数
        //  auto表示由编译器自动推断f的型式
        auto  f = std::bind(&Worker<int>::ThreadFunc,
            std::get<0>(v[i]), std::placeholders::_1);

        //  动态构造线程对象,并保存到向量的第i个三元组中
        //  传递三元组的1号元地址,即将该地址作为线程函数的参数
        //  线程将在执行时将结果写入该地址
        //  此性质由绑定函数std::bind()使用占位符std::placeholders::_1指定
        //  线程对象为2号元,即三元组的最后一个元素
        std::get<2>(v[i]) = new std::thread(f, &std::get<1>(v[i]));
    }
    for (int i = 0; i < 8; i++)
    {
        //  等待线程结束
        std::get<2>(v[i])->join();
        //  销毁劳工对象
        delete std::get<0>(v[i]), std::get<0>(v[i]) = nullptr;
        //  销毁线程对象
        delete std::get<2>(v[i]), std::get<2>(v[i]) = nullptr;
    }
    //  输出线程计算后的结果
    for (int i = 0; i < 8; i++)
        std::cout << "No. " << i << ": result = " << std::get<1>(v[i]) << std::endl;
    return 0;
}
image.png
//  使用期许对象获取线程返回值
#include <iostream>
#include <exception>
#include <thread>
#include <future>

unsigned long int  CalculateFactorial(short int n)
{
    unsigned long int  r = 1;
    if (n > 20)
        throw std::range_error("The number is too big.");
    for (short int i = 2; i <= n; i++)
        r *= i;
    return r;
}
int main()
{
    short int  n = 20;
    //  启动异步线程,执行后台计算任务,并返回std::future对象
    std::future<unsigned long int>  f = std::async(CalculateFactorial, n);
    try  {
        //  获取线程返回值,若线程已结束,立即返回,否则等待该线程计算完毕
        //  若线程引发异常,则延迟到std::future::get()或std::future::wait()调用时引发
        unsigned long int  r = f.get();
        std::cout << n << "! = " << r << std::endl;
    }
    catch (const std::range_error & e)  {
        std::cerr << e.what() << std::endl;
    }
    return 0;
}
image.png
//  使用承诺对象设置线程返回值
#include <iostream>
#include <exception>
#include <thread>
#include <future>

unsigned long int CalculateFactorial(short int n)
{
    unsigned long int r = 1;
    if (n > 20)    throw std::range_error("The number is too big.");
    for (short int i = 2; i <= n; i++)    r *= i;
    return r;
}

//  CalculateFactorial()函数的包装函数原型
void DoCalculateFactorial(
    std::promise<unsigned long int> && promise, short int n);
int main()
{
    short int  n = 6;
    std::promise<unsigned long int>  p;    //  创建承诺对象
    std::future<unsigned long int>  f = p.get_future();    //  获取相关期许对象
    //  启动线程,执行CalculateFactorial()函数的包装函数
    std::thread  t(DoCalculateFactorial, std::move(p), n);
    t.detach();
    try
    {
        unsigned long int  r = f.get();
        std::cout << n << "! = " << r << std::endl;
    }
    catch (std::range_error & e)
    {
        std::cerr << e.what() << std::endl;
    }
    return 0;
}
//  CalculateFactorial()函数的包装函数实现
void DoCalculateFactorial(
    std::promise<unsigned long int> && promise, short int n)
{
    try
    {
        //  设置线程返回值,供期许对象获取
        promise.set_value(CalculateFactorial(n));
    }
    catch (...)
    {
        //  捕获全部异常,并在期许获取线程返回值时重新引发
        promise.set_exception(std::current_exception());
    }
}

整理详细的博客:https://blog.csdn.net/MoreWindows/article/details/7392749

上一篇下一篇

猜你喜欢

热点阅读