『 C++ 』线程库

10 篇文章 0 订阅
6 篇文章 0 订阅


线程库

请添加图片描述

C++标准库提供了一套完整的线程支持库,从C++11开始引入,并在后续版本中不断增强;

这些库包括用于创建和管理线程的类,以及多种并发工具,如互斥锁,条件变量,原子操作等;

C++中的线程库根据不同的操作系统平台其底层支持不同,以Linux为例用的线程库为原生pthread线程库提供,即在Linux下使用C++的线程库时必须链接对应的pthread;

g++ -o a.out main.cc -lpthread -std=c++11

pthread线程库是一个POSIX标准的线程库(POSIX指可以指操作系统接口),可适用于Linux,Unix,MacOS X等操作系统;

Windows下其也提供了一个单独的线程库,Windows下使用C++的线程库即为Windows线程库的封装;

LinuxC++线程库为对pthread线程库的封装;

语言层面和系统层面进行解耦合,在使用C++提供的线程库时只需要包含<thread>头文件即可(Linux下还是需要在编译时链接pthread库,否则无法使用);

将操作系统提供的线程库通过封装为一个类(std::thread)以面向对象;

其提供了一系列的成员函数用于线程的使用(参考 [ Link - C++ Reference thread ]);


线程的创建与销毁

请添加图片描述

C++线程库提供了一系列的构造函数用于创建线程;

  • 带参构造

    template <class Fn, class... Args>
    explicit thread (Fn&& fn, Args&&... args);
    

    线程库提供了一个万能引用与带有一个可变参数包的构造函数用于构造一个带参数的线程对象实例;

    • Fn&& fn

      该参数是一个万能引用参数,可以根据传递的数据属性自动推导其左值或是右值属性;

      这个参数表示需要传入一个可调用对象,这个可调用对象可以是仿函数,函数指针,bind绑定后的函数对象,function包装器包装后的函数对象以及Lambda表达式等;

      所传入的可调用对象将称为该线程的入口点;

    • Args&&... args

      这个可变参数包同样的是一个万能引用参数,既能够自动推导所传入参数的左右值属性,同时作为可变参数包可自动推导所传入的参数类型;

      这个可变参数包表示传入给可调用对象Fn&& fn的参数;

    void Print(int n) { // 定义一个打印函数
      for (int i = 0; i < n; ++i) {
        cout << n << " ";
      }
      cout << endl;
    }
    
    int main() {
      thread td1(Print, 5); // 线程 td1 调用 Print 作为可调用对象
      thread td2( 
          [](int n) { // 线程 td2 调用 lambda 表达式作为可调用对象
            for (int i = 0; i < n; ++i) {
              cout << "td2" << endl;
            }
          },3);
        
      // join 用于等待线程结束
      td1.join();
      td2.join();
      return 0;
    }
    

    当一个线程带参构造时对应的可调用对象将成为该线程的入口点,对应的存在可调用对象时线程对象在实例化后将自行启动;

  • 无参构造

    thread() noexcept;
    

    线程库提供了一个无参构造函数用于实例化一个空的线程对象;

    空的线程对象可通过移动构造或是移动赋值将一个非空线程实例资源转移至该空线程实例上以进行使用;

    thread td; // 实例化一个空的线程对象
    

    实例化的空线程不会启动;

  • 拷贝构造

    thread (const thread&) = delete;
    

    线程的拷贝是一个危险的动作,thread线程不支持任何拷贝操作(无论是拷贝构造还是拷贝赋值);

  • 移动构造

    thread (thread&& x) noexcept;
    

    线程支持移动(移动构造或是移动拷贝)操作,即将一个将亡值线程对象中的属性资源转移给另一个线程;

    int main() {
      thread td1(
          [](int n) {
            for (int i = 0; i < n; ++i) {
              cout << "td2" << endl;
            }
          },
          3);
    
      thread td2(move(td1)); // 移动构造
      thread td3;
      td3 = move(td2); // 移动赋值 
      td3.join();
      return 0;
    }
    

    在使用移动时必须保证接收移动资源的线程实例必须是一个空线程,否则会因为目标线程对象已经管理了一个活动线程而导致资源双重管理或丢失产生的未定义行为;

    int main() { // 未定义行为
      thread td1(Print, 5);
      thread td2(
          [](int n) {
            for (int i = 0; i < n; ++i) {
              cout << "td2" << endl;
            }
          },3);
    
      td2 = move(td1); // 线程 td2 为非空线程
      td2.join();
      return 0;
    }
    /*
    	运行结果为:
    	$ ./thread 
        terminate called without an active exception
        Aborted
        # 运行崩溃
    */
    
  • 析构函数

    ~thread();
    

    析构函数用于销毁该线程实例;

    在调用析构函数时必须保证该线程实例是join后的,否则将存在未定义行为;


成员函数

请添加图片描述

  • thread::detach()

    void detach();
    

    该成员函数用于分离一个线程,被分离的线程不需要显式join,将会成为一个不可联结的线程;

    void Print(int n) {
      for (int i = 0; i < n; ++i) {
        cout << i << " ";
      }
      cout << endl;
    }
    
    int main() {
      thread t1(Print, 3);
      t1.detach();
      sleep(1);  // 防止主线程过早结束
      return 0;
    }
    /*
    	运行结果:
        $ ./thread 
        0 1 2 
    */
    
  • thread::get_id()

    id get_id() const noexcept;
    

    该函数用于返回该线程的线程ID;

    其中类型idC++线程库中自定义的一个类型,为一个自定义类型;

    Linux - CentOS7中被一个哈希表所存储,其中结构体中保存着该线程的基本属性,通过重载operator<<流插入实现打印哈希表对应的key值,即线程id;

    int main() {
      thread t1(Print, 3);
    
      printf("printf id : %llu\n", t1.get_id());
      cout << "cout id : " << t1.get_id() << endl;
      cout << typeid(t1.get_id()).name() << endl; // 打印 id 的类型名
    
      t1.join();
    
      return 0;
    }
    
    /*
    	运行结果:
    	 // Linux
            $ ./thread 
            printf id : 140061102868224
            cout id : 140061102868224
            NSt6thread2idE
            0 1 2 
    	 // Windows
    	 	printf id : 28396
            cout id : 28396
            class std::thread::id
            0 1 2
    */
    
  • thread::join()

    void join();
    

    该函数用于等待线程结束;

    已经被detach()的线程不能使用join进行等待,否则会出现未定义行为;

    int main() {
      thread t1([] { cout << "t1" << endl; });
      thread t2([] { cout << "t2" << endl; });
        
      t1.detach(); // t1 进行 detach
    
      // 对两个线程进行 join
      t1.join();
      t2.join();
    
      return 0;
    }
    
    /*
    	运行结果:
    	$ ./thread 
        t2    // t2 正常
        terminate called after throwing an instance of 'std::system_error'
          what():  Invalid argument
        Aborted // t1 已经被 detach , 再次 join 时出现未定义行为
    */
    

    运行结果中t1正常运行,t2已经被detach,对t2进行join时出现未定义行为,程序异常退出;

  • thread::joinable()

    bool joinable() const noexcept;
    

    该函数用于判断该线程是否为一个可联结(未join/detach且已经启动)线程,是则返回true,否则返回false;

    int main() {
      thread t1([] { cout << "t1" << endl; });
      thread t2([] { cout << "t2" << endl; });
      t1.detach();
      cout << t1.joinable() << endl;
      cout << t2.joinable() << endl;
      t2.join();
    
      return 0;
    }
    /*
    	运行结果:
    	$ ./thread 
        0
        1
        t2
        t1
    */
    

    运行结果t1detach后不为可联结线程返回false,t2join且未detach为一个可联结线程,返回true;

  • thread::swap()

    void swap (thread& x) noexcept;
    

    该函数用于交换两个线程;

    int main() {
      thread t1([] { cout << "t1" << endl; });
      thread t2([] { cout << "t2" << endl; });
    
      t1.swap(t2);
      t1.join();
      t2.join();
    
      return 0;
    }
    
  • operator=()

    重载了赋值操作符;

    thread& operator= (thread&& rhs) noexcept;
    thread& operator= (const thread&) = delete;
    

    不支持拷贝赋值,支持移动赋值;

    可通过其他容器,无参构造和移动赋值来实现管理多个线程;

    void Print(const string& str, int n) { cout << str << n << endl; }
    
    int main() {
      int n = 5;
      vector<thread> vths(n);
      for (int i = 0; i < n; ++i) {
        vths[i] = thread(Print, "线程", i); // 其中 thread(Print, "线程", i) 为将亡值
      }
    for(auto& th:vths){
      th.join();
    }
      return 0;
    }
    /*
    	运行结果为:
    	$ ./thread 
        线程1
        线程3
        线程4
        线程2
        线程0
    */
    

this_thread 命名空间

请添加图片描述

std::this_thread命名空间是C++标准库中的一个命名空间,提供了一系列与当前执行线程相关的函数;

  • this_thread::get_id()

    thread::id get_id() noexcept;
    

    该函数用于获取当前执行线程中的线程id;

    void Print(const string& str, int n) {
      cout << str << n << " the id : " << this_thread::get_id() << endl; // 获取当前线程 id
    }
    
    int main() {
      int n = 5;
      vector<thread> vths(n);
      for (int i = 0; i < n; ++i) {
        vths[i] = thread(Print, "线程", i);
      }
      for (auto& th : vths) {
        th.join();
      }
      return 0;
    }
    /*
    	运行结果为:
    	$ ./thread 
        线程3 the id : 140560757815040
        线程1 the id : 140560774600448
        线程4 the id : 140560749422336
        线程2 the id : 140560766207744
        线程0 the id : 140560782993152
    */
    
  • this_thread::sleep_for()

    template <class Rep, class Period>
      void sleep_for (const chrono::duration<Rep,Period>& rel_time);
    

    该函数用于阻塞当前线程一段时间;

    其中chrono为一个命名空间,提供一系列用于处理时间和时钟的工具和类型;

  • this_thread::sleep_until()

    template <class Clock, class Duration>
      void sleep_until (const chrono::time_point<Clock,Duration>& abs_time);
    

    该函数用于阻塞当前执行线程时间至abs_time时间点;

  • this_thread::yield()

    void yield() noexcept;
    

    该函数的作用为在多线程环境中允许当前线程主动让出处理器的执行权使得操作系统调度其他线程执行;

    当一个线程调用该函数时将会提示操作系统当前线程愿意让出处理器的时间片,允许调度器切换到另一个线程执行以提升多线程程序的整体性能和响应性;


线程的引用传值

请添加图片描述

在对线程进行引用传值时需要在传值中使用ref()以确保所传递的引用被完美转发;

因为在进行传引用传值时所传数据不会直接被线程的入口函数接收,而是首先被线程thread的构造函数接收;

使用ref以确保被构造函数接收后还能保持其左值或右值引用属性向下传递给线程的入口函数;

void Func1(mutex &mtx, int &x) {
  for (int i = 0; i < 10000; ++i) {
    mtx.lock();
    ++x;
    mtx.unlock();
  }
  cout << this_thread::get_id() << " : Func1" << endl;
}

int main() {
  mutex mtx;
  int x = 0;
  thread t1(Func1, ref(mtx), ref(x));
  thread t2(Func1, ref(mtx), ref(x));

  t1.join();
  t2.join();
  cout << x << endl;

  return 0;
}
/*
	运行结果:
	$ ./thread 
    139676657420032 : Func1
    139676665812736 : Func1
    20000
*/

互斥锁

请添加图片描述

C++在引入线程库时也引入了对应的用于同步操作的锁,即mutex,用于支持多线程情况下的相关操作;

如一些需要保护临界资源避免产生竞态条件的情况;

mutex需要包含<mutex>头文件;


互斥锁的基本操作

请添加图片描述

  • 互斥锁的创建

    C++中互斥锁也被封装为一个类,在使用锁之前需要实例化一个锁对象;

    mutex mtx; // 实例化一个互斥锁对象
    
  • 加锁与解锁

    调用成员函数mutex::lock()用于互斥锁的加锁,若是互斥锁被其他线程占有则阻塞等待;

    调用成员函数mutex::unlock()用于互斥锁的解锁;

    调用成员函数mutex::try_lock()用于互斥锁的加锁,若是互斥锁被其他线程占有则调用失败函数返回;

mutex互斥锁同样的不支持拷贝操作只支持移动操作(移动构造mutex(mutex&&)与移动赋值operator=(mutex&&));

int main() {
  mutex mtx;
  size_t a = 0, b = 0, x = 0;
  cin >> a >> b;
  thread t1([a, &x,&mtx] { // 以引用的方式捕获互斥锁
    for (size_t i = 0; i < a; i++) {
      mtx.lock(); // 加锁
      ++x;
      mtx.unlock(); // 解锁
    }
  });
  thread t2([b, &x, &mtx] {
    for (size_t i = 0; i < b; i++) {
      mtx.lock();
      ++x;
      mtx.unlock();
    }
  });

  t1.join();
  t2.join();
  cout << x << endl;
}
/*
	运行结果:
	$ ./thread 
    100000 100000
    200000
*/

在这个例子中实例化一个互斥锁对象并且使用lockunlock成员函数用于保护临界资源从而保证线程安全;


递归锁(可重入锁)

请添加图片描述

递归锁是C++引入的一个用于避免在递归状态下导致死锁问题的锁;

class recursive_mutex;

其成员函数与mutex相同;

当一个线程在使用mutex互斥锁时将会因为重入互斥锁导致死锁问题;

void Func1(mutex &mtx, int &x) {
  if (!x) return;
  mtx.lock();
  cout << this_thread::get_id() << " : Func1" << endl;
  Func1(mtx, --x);
  mtx.unlock();
}

int main() {
  mutex mtx; // 实例化一个互斥锁
  int x = 5; 
  thread t1(Func1, ref(mtx), ref(x));
  t1.join();

  return 0;
}
/*
	运行结果:
	$ ./thread 
    139666051618560 : Func1
    ^C // 死锁 - ctrl + C 结束程序
*/

递归锁则在递归调用过程中判断lock时的线程是不是同一个线程,若是一个持有锁的线程再次lock时会判断是否为同一个线程,为同一个线程则不再次获取锁或是等待,非同一线程则阻塞;

void Func1(recursive_mutex &rmtx, int &x) {
  if (!x) return;
  rmtx.lock();
  cout << this_thread::get_id() << " : Func1" << endl;
  Func1(rmtx, --x);
  rmtx.unlock();
}

int main() {
  recursive_mutex rmtx; // 实例化一个可重入互斥锁
  int x = 5;
  thread t1(Func1, ref(rmtx), ref(x));
  t1.join();

  return 0;
}
/*
	运行结果:
	$ ./thread 
    140547591325440 : Func1
    140547591325440 : Func1
    140547591325440 : Func1
    140547591325440 : Func1
    140547591325440 : Func1
*/

定时互斥锁

请添加图片描述

class timed_mutex; // 定时互斥锁
class recursive_timed_mutex; // 可重入定时互斥锁

定时互斥锁与可重入定时互斥锁使用方式与互斥锁/可重入互斥锁相同;

定时获取互斥锁的方式提供了两种,分别为try_lock_fortry_lock_until,一个用于定时时间段,一个用于定时具体时间点;

这两个获取互斥锁的方式都是以try的方式,即尝试获取锁(定时互斥锁/可重入定时互斥锁),若是在对应的时间段过后或者具体时间点时该锁被其他线程占有则调用失败返回false;

通常情况下定时互斥锁用于在多线程环境中,允许线程在有限的时间内尝试获取资源而不是无限期的等待;


互斥锁管理器与互斥锁抛异常所引发的死锁问题

请添加图片描述

互斥锁在锁定后抛异常将出现死锁问题;

int main() {
  mutex mtx;
  thread t1([&mtx] {
    try {
      mtx.lock();  // 占用互斥锁
      throw "t1 Throw an exception";  // 抛出一个异常
      mtx.unlock();  // 解锁操作 - 抛出异常后该操作无法进行
    } catch (const char* str) {
      cout << str << endl;
    }
  });
  thread t2([&mtx] {
    try {
      mtx.lock();                  
      throw "t2 Throw an exception";  
      mtx.unlock();  
    } catch (const char* str) {
      cout << str << endl;
    }
  });

  t1.join();
  t2.join();

  return 0;
}
/*
	运行结果:
	$ ./thread 
    t1 Throw an exception
    ^C // 发生死锁
*/

在这个例子中两个线程t1t2在执行过程中必现死锁现象,原因是无论是哪个线程占有互斥锁后都会抛出一个异常跳到对应的catch位置处理异常从而忽略unlock解锁;

该问题通常需要使用RAII来解决问题;

class testGuard { // RAII 
 public:
  testGuard(mutex& mtx) : _mtx(mtx) { mtx.lock(); } // 资源创建即初始化
  ~testGuard() { _mtx.unlock(); } // 

 private:
  mutex& _mtx;
};

int main() {
  mutex mtx;
  thread t1([&mtx] {
    try {
      testGuard(ref(mtx));
      throw "t1 Throw an exception";
    } catch (const char* str) {
      cout << str << endl;
    }
  });
  thread t2([&mtx] {
    try {
      testGuard(ref(mtx));
      throw "t2 Throw an exception";
    } catch (const char* str) {
      cout << str << endl;
    }
  });

  t1.join();
  t2.join();

  return 0;
}

这个例子中使用了RAII的模式来管理互斥锁的生命周期以避免资源泄露和死锁问题;

当资源初始化时则锁定互斥锁,出了该作用域调用析构时则自动释放互斥锁;

在标准库中提供了对应的用于管理互斥锁生命周期的类,即std::lock_guardstd::unique_lock;

  • std::lock_guard

    该互斥锁管理器是一个轻量级的,非可重入的互斥锁管理器;

    当一个lock_guard对象被创建时将会自动尝试获取一把给定的锁;

    当该对象销毁时(通常为作用域结束)时将会自动释放该锁;

    int main() {
      mutex mtx;
      thread t1([&mtx] {
        try {
          lock_guard<mutex>(ref(mtx)); // 使用互斥锁管理器管理互斥锁的生命周期以避免抛异常后产生的死锁问题
          throw "t1 Throw an exception";
        } catch (const char* str) {
          cout << str << endl;
        }
      });
      thread t2([&mtx] {
        try {
          lock_guard<mutex>(ref(mtx));
          throw "t2 Throw an exception";
        } catch (const char* str) {
          cout << str << endl;
        }
      });
    
      t1.join();
      t2.join();
    
      return 0;
    }
    /*
    	运行结果:
        $ ./thread 
        t1 Throw an exception
        t2 Throw an exception
    */
    
  • std::unique_lock

    该互斥锁管理器比lock_guard更加灵活,提供了延迟锁定,提前解锁或显式重新锁定,并且支持不同种类的同步机制,如条件变量;

    更加灵活提供了更多功能也表示其对lock_guard更高的开销;

    同样当unique_lock生命周期结束时将会解锁与之关联的互斥锁;

    int main() {
      mutex mtx;
      thread t1([&mtx] {
        try {
          unique_lock<mutex> m(ref(mtx));
          m.unlock(); // 解锁
          m.lock(); // 重新锁定
          // 使用互斥锁管理器管理互斥锁的生命周期以避免抛异常后产生的死锁问题
          throw "t1 Throw an exception";
        } catch (const char* str) {
          cout << str << endl;
        }
      });
      thread t2([&mtx] {
        try {
          unique_lock<mutex>(ref(mtx));
          throw "t2 Throw an exception";
        } catch (const char* str) {
          cout << str << endl;
        }
      });
    
      t1.join();
      t2.join();
    
      return 0;
    }
    
    /*
    	运行结果:
    	$ ./thread 
        t1 Throw an exception
        t2 Throw an exception
    */
    

条件变量

请添加图片描述

C++标准在引入线程库时为了支持线程间的同步操作不仅提供了互斥锁同样的也提供了条件变量std::condition_variable;

class condition_variable;

同样的条件变量只支持构造不支持拷贝操作;

default (1)	
	condition_variable();
copy [deleted] (2)	
	condition_variable (const condition_variable&) = delete;

条件变量的等待

请添加图片描述

C++的条件变量提供了三种等待(wait)的方式,分别为condition_variable::wait,condition_variable::wait_for,condition_variable::wait_until;

无论是哪种wait方式所传入的参数必须是一个使用unique_lock互斥锁管理器的互斥锁;

原因是通常情况下条件变量的使用是与互斥锁相互配合的,在判断条件变量条件时需要将互斥锁提前进行解锁;

lock_guard互斥锁管理器没有提供相应的提前解锁操作,因此在使用条件变量等待场景中必须使用unique_lock;

  • wait

    	void wait (unique_lock<mutex>& lck);
    
    	template <class Predicate>
      	void wait (unique_lock<mutex>& lck, Predicate pred);
    
    • void wait (unique_lock<mutex>& lck)

      该条件变量等待为传入一个使用unique_lock互斥锁管理器的互斥锁,当一个持有该互斥锁的线程wait时将会进行一次unlock解锁操作;

      若该线程未持有该互斥锁则直接进行wait等待,不进行其他操作;

    • void wait (unique_lock<mutex>& lck, Predicate pred)

      该条件变量等待是一个重载版本,可以根据传入的谓词pred来等待特定条件;

      该谓词再等待和唤醒时会被持续调用,以确保条件满足后才继续执行;

  • wait_for

    template <class Rep, class Period>
      cv_status wait_for (unique_lock<mutex>& lck,const chrono::duration<Rep,Period>& rel_time);
    
    template <class Rep, class Period, class Predicate>
           bool wait_for (unique_lock<mutex>& lck,const chrono::duration<Rep,Period>& rel_time, Predicate pred);
    
    • cv_status wait_for (unique_lock<mutex>& lck,const chrono::duration<Rep,Period>& rel_time)

      该等待操作允许线程等待指定的时间段,同时管理互斥锁和条件变量;

      这个函数将会返回一个枚举类型cv_status表明它是被唤醒(通过notify_onenotify_all)还是超时了;

    • bool wait_for (unique_lock<mutex>& lck,const chrono::duration<Rep,Period>& rel_time, Predicate pred)

      同样的该等待方式提供了一个添加传入 谓词 来检查是否满足条件的一种等待方式;

      这个版本的wait_for返回一个bool类型标识再指定时间内谓词是否变为true;

  • wait_until

    template <class Clock, class Duration>
      cv_status wait_until (unique_lock<mutex>& lck,const chrono::time_point<Clock,Duration>& abs_time);
    
    template <class Clock, class Duration, class Predicate>
           bool wait_until (unique_lock<mutex>& lck, const chrono::time_point<Clock,Duration>& abs_time, Predicate pred);
    
    • cv_status wait_until (unique_lock<mutex>& lck,const chrono::time_point<Clock,Duration>& abs_time)

      提供了一个wait_until方法,该方法允许线程等待直到某个特定的绝对时间点;

      这个方法返回一个枚举类型表明该线程是被唤醒还是超时;

    • bool wait_until (unique_lock<mutex>& lck, const chrono::time_point<Clock,Duration>& abs_time, Predicate pred)

      提供了一个带有谓词参数的wait_until方法;

      允许线程等待直到某个特定的绝对时间点并在等待过程中检查特定条件(谓词)是否满足;

      该方法返回了一个bool类型指示再给定时间内谓词是否为true;


条件变量的唤醒

请添加图片描述

C++提供的条件变量提供了两种唤醒的方式,分别为notify_onenotify_all,分别表示唤醒条件变量等待队列中的一个线程与唤醒条件变量等待队列中的所有线程;

  • notify_one()

    void notify_one() noexcept;
    

    用于唤醒条件变量等待队列中的一个线程;

    如果等待队列中不存在任何线程则什么都不做;

  • notify_all()

    void notify_all() noexcept;
    

    用于唤醒条件变量等待队列中的所有线程;

    如果等待队列中不存在任何线程则什么都不做;

通常情况下在使用notify_all应谨慎以避免产生竞态条件与无效唤醒导致无意义的增加系统开销;


两个线程交替打印奇偶数

请添加图片描述

假设存在两个线程为t1t2,这两个线程需要交替打印奇偶数;

在使用交替打印的时候必须使用条件变量进行控制;

#include <iostream>
#include <thread>
#include <condition_variable>

using namespace std;

int main() {
  int last = 0;
  cin >> last;  // 获取用户输入的最后一个数
  condition_variable cv;  // 条件变量,用于线程同步
  int x = 1;              // 临界资源,表示当前计数值
  bool isOdd = true;      // 状态变量,用于控制线程应该打印的是奇数还是偶数
  mutex mtx;              // 互斥锁,保护临界资源

  // 线程1:用于打印奇数
  thread t1([&, last] {
    int tmp = last / 2;  // 循环次数为输入的一半

    for (int i = 0; i < tmp; ++i) {
      unique_lock<mutex> umtx(mtx);  // 使用互斥锁保护共享资源

      // 使用传统的while循环进行条件等待
      while (!isOdd) cv.wait(umtx);

      // 提示:你也可以直接使用带谓词的`wait`方法代替上面的while循环
      // cv.wait(umtx, [&] { return isOdd; });
      // 带谓词的`wait`方法,当lambda表达式返回`false`时线程会被阻塞

      cout << "t1 : " << x << endl;  // 打印当前奇数
      ++x;  // 增加计数
      isOdd = false;  // 设置状态为false,下一个应该打印偶数
      cv.notify_one();  // 通知等待中的线程t2
    }
  });

  // 线程2:用于打印偶数
  thread t2([&, last] {
    int tmp = last / 2;  // 循环次数为输入的一半

    for (int i = 0; i < tmp; ++i) {
      unique_lock<mutex> umtx(mtx);  // 使用互斥锁保护共享资源

      // 使用传统的while循环进行条件等待
      while (isOdd) cv.wait(umtx);

      // 提示:你也可以直接使用带谓词的`wait`方法代替上面的while循环
      cv.wait(umtx, [&] { return !isOdd; });  
      // 带谓词的 wait 方法,当lambda表达式返回 false 时线程会被阻塞

      cout << "t2 : " << x << endl;  // 打印当前偶数
      ++x;   // 增加计数
      isOdd = true;   // 设置状态为true,下一个应该打印奇数
      cv.notify_one();   // 通知等待中的线程t1
    }
  });

  t1.join();   // 等待线程t1执行完毕
  t2.join();   // 等待线程t2执行完毕
}
/*
	运行结果:
	$ ./thread 
    6
    t1 : 1
    t2 : 2
    t1 : 3
    t2 : 4
    t1 : 5
    t2 : 6
*/

在这个例子中的两个线程,t1用于打印奇数,t2用于打印偶数;

输入了一个值作为标准值,两个线程打印最终会从1开始交替打印奇偶数,最终打印至标准值;

  • 共享资源和同步机制

    • mutex mtx

      两个线程共用一个互斥锁来保护临界资源x和状态变量isOdd;

    • condition_variable cv

      用于协调两个线程对临界资源的访问,确保只有一个线程对共享资源的访问,确保每次只有一个线程能够访问;

  • 线程1(打印奇数)

    • 使用unique_lock<mutex> umtx(mtx)锁定互斥锁;
    • 使用cv.wait(umtx, [&]{ return isOdd; })等待直到isOddtrue;
    • 打印并修改临界资源后将isOdd设置为true并通知另一个线程;
  • 线程2(打印偶数)

    • 使用unique_lock<mutex> umtx(mtx)锁定互斥锁;
    • 使用cv.wait(umtx, [&]{ return isOdd; })等待直到isOddfalse;
    • 打印并修改临界资源后将isOdd设置为false并通知等待另一个线程;

这个例子中t1打印奇数t2打印偶数,如果t2先比t1拿到互斥锁,但由于条件变量条件不满足将会wait进行阻塞;

t2进行阻塞时将会释放互斥锁,t1将会占用互斥锁并通过条件变量进行一轮操作直至下一轮操作时条件变量条件不满足时将会被阻塞,但在此前的一轮操作中已经使用notify_one唤醒了另一个线程;

如此往复;

  • 7
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dio夹心小面包

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值