线程支持库包含了线程,互斥锁,线程条件变量(class thread),定义于<thread>
线程提供一个并发的实例,需要对应一个"线程函数"
线程的主要任务就是去执行这个"线程函数"
既然线程需要提供一个线程函数,在线程实例化的时候,需要一个"线程函数"
构造一个线程类的对象,需要提供一个参数(线程函数)
C++的线程类就是包装了POSIX标准的线程接口,所以在编译的时候需要链接多线程库
-lpthread最基本的用法:
thread t{线程函数,线程函数的参数......};// 实例化一个线程对象,创建成功之后,就会自动运行
t.join();// 等待线程退出
01 线程类的用法(创建一个线程,让新创建的线程完成指定的功能)
(1) 线程函数是全局函数
cpp#include <iostream> #include <thread> using namespace std; // 全局的线程函数(按值传递) void print_arg(int a) { int c = 10; while (c--) { cout << "a:" << a++ <<endl; // 线程休眠一段时间 this_thread::sleep_for(1s); } } // 全局的线程函数(按引用传递) void print_arg1(int & a) { // a成为你实际传入的参数的别名 int c = 10; while (c--) { cout << "a:" << a++ <<endl; //线程休眠一段时间 this_thread::sleep_for(1s); } } int main() { // 实例化一个线程对象,自动开始运行 // thread t{线程函数, 线程函数的参数}; int n = 10; // thread t{print_arg, n}; // 创建一个线程,按值传递参数 thread t{print_arg1, std::ref(n)}; // 创建一个线程,按引用传递参数 // 传参的时候,指定n以引用的方式传递给线程的构造函数 /* thread(线程函数地址, int x = n) { // 线程的构造函数 pthread_create(...线程函数地址,&x); } */ // 完成其他的任务 int c = 10; while (c--) { cout << "hello" <<endl; // 线程休眠一段时间 this_thread::sleep_for(1s); } // 等待线程结束 t.join(); // 阻塞 cout << "n:" << n << endl; return 0; }
(2) 线程函数是类的static函数
类的static函数
a. 可以通过类名调用
b. 没有this指针
c. 只能访问类的静态成员(不能访问类的非static成员)
使用方式:
thread t{类名::静态成员函数名,线程函数的参数};
cpp// 线程函数是类的static函数 #include <iostream> #include <thread> using namespace std; class A { public: // 线程函数(按值传递) static void print_arg(int a) { int c = 10; while (c--) { cout << "a:" << a++ <<endl; // 线程休眠一段时间 this_thread::sleep_for(1s); } } // 线程函数(按引用传递) static void print_arg1(int & a) { // a成为你实际传入的参数的别名 int c = 10; while (c--) { cout << "a:" << a++ <<endl; // 线程休眠一段时间 this_thread::sleep_for(1s); } } }; int main() { // 实例化一个线程对象,自动开始运行 // thread t{线程函数, 线程函数的参数}; int n = 10; // A::print_arg() // thread t{A::print_arg, n}; // 创建一个线程,按值传递参数 thread t{A::print_arg1, std::ref(n)}; // 创建一个线程,按引用传递参数 // 传参的时候,指定n以引用的方式传递给线程的构造函数 // 完成其他的任务 int c = 10; while (c--) { cout << "hello" <<endl; // 线程休眠一段时间 this_thread::sleep_for(1s); } // 等待线程结束 t.join(); // 阻塞 cout << "n:" << n << endl; return 0; }
(3) 线程函数是类的普通成员函数
普通的全局函数和静态函数都可以直接调用,不需要通过对象,但是类的普通成员函数不能直接调用,必须通过对象调用(因为有一个隐式的参数:this)
所以,当一个类的非静态成员函数作为线程函数时,我们需要传递一个该类的对象的地址作为this指针的实参
语法:
thread t{&类名::成员函数名,该类对象的地址,线程函数的参数};
cpp// 线程函数是类的非static函数 #include <iostream> #include <thread> using namespace std; class A { public: // 线程函数(按值传递) void print_arg(int a) { int c = 10; while (c--) { cout << "a:" << a++ <<endl; // 线程休眠一段时间 this_thread::sleep_for(1s); } } // 线程函数(按引用传递) void print_arg1(int & a) { // a成为你实际传入的参数的别名 int c = 10; while (c--) { cout << "a:" << a++ <<endl; // 线程休眠一段时间 this_thread::sleep_for(1s); } } }; int main() { // 实例化一个线程对象,自动开始运行 // thread t{线程函数, 线程函数的参数}; int n = 10; A a; // 实例化一个对象 // a.print_arg(n) -----> print_arg(&a, n) // thread t{&A::print_arg, &a, n}; // 创建一个线程,按值传递参数 thread t{&A::print_arg1, &a, std::ref(n)}; // 创建一个线程,按引用传递参数 // 传参的时候,指定n以引用的方式传递给线程的构造函数 // 完成其他的任务 int c = 10; while (c--) { cout << "hello" <<endl; // 线程休眠一段时间 this_thread::sleep_for(1s); } // 等待线程结束 t.join(); // 阻塞 cout << "n:" << n << endl; return 0; }
以上三种就是线程最普通的用法
02 线程互斥锁(mutex)
线程互斥锁是用来避免多个并发实例对共享资源的访问产生竞争,定义于头文件<mutex>
class mutex;
class timed_mutex;
std::mutex 既不可以复制,也不可以移动(删除了operator=,也没有实现移动构造函数)
默认构造函数可以初始化一个锁对象,默认是处于解锁状态的
函数:lock 上锁,阻塞到获取锁
try_lock 尝试获取锁,获取失败则返回false
unlock 解锁
用法:
mutex m;// 实例化一个锁对象
m.lock(); // 访问共享资源前上锁 m.try_lock();
......; // 访问共享资源的代码(临界区)
m.unlock(); // 访问完共享资源后解锁
cpp#include <iostream> #include <thread> #include <mutex> using namespace std; mutex m; // 全局的锁 // 全局的共享资源 int x = 0; // 是一个不可重入的函数 void add_x() { int c = 1000000; while (c--) { // while (!m.try_lock()); // 上锁 m.lock(); x++; // 共享资源 m.unlock(); } } int main() { // 实例化一个线程对象,自动开始运行 thread t1{add_x}; thread t2{add_x}; // 等待线程结束 t1.join(); // 阻塞 t2.join(); // 阻塞 cout << "x:" << x << endl; return 0; }
mutex,timed_mutex ...... 可以单独使用,但是可能会遇到一些问题
如:(1) 程序员不注意,造成了"带锁退出" ------> deadlock
xxx() {
m.lock();
......
if (...) {
return ;
}
m.unlock();
}
(2) 同时获取多个锁,推进的顺序不一样
xxx() {
m1.lock();
m2.lock();
......
......
m2.unlock();
m1.unlock();
}
yyy() {
m2.lock();
m1.lock();
......
......
m1.unlock();
m2.unlock();
}
基于这样的原因,C++标准库中,提供了一些互斥锁的包裹类
常用的是:
std::lock_guard<std::mutex> guard{m};
std::unique_lock<std::mutex> lock{m};
可以自动的管理指定的锁,在代码作用域结束后,可以自动的释放指定的锁,可以防止带锁退出
如:
mutex m; // 普通的互斥锁,默认是处于解锁状态的
{ // 描述的是一个作用域范围
std::lock_guard<std::mutex> guard{m}; // 使用一个管理对象guard管理锁m
// ...... 临界区代码
return ;
}
上面的代码是使用lock_guard类型去管理m这个锁,当构造guard的时候自动的给m表示的锁上锁,当guard超出作用域范围后,guard管理的锁自动的解锁
cpp#include <iostream> #include <thread> #include <mutex> using namespace std; mutex m; // 全局的锁 // 全局的共享资源 int x = 0; // 是一个不可重入的函数 void add_x() { int c = 1000000; while (c--) { lock_guard<mutex> g{m}; // 使用包裹类对象g管理m表示的锁 x++; // 共享资源 } } int main() { // 实例化一个线程对象,自动开始运行 thread t1{add_x}; thread t2{add_x}; // 等待线程结束 t1.join(); // 阻塞 t2.join(); // 阻塞 cout << "x:" << x << endl; return 0; }
unique_lock是lock_guard的升级版,提供了额外的接口
也能够在超出作用域范围之后,自动的释放管理的锁
用于指定锁定策略的标签常量(常量):
defer_lock
try_to_lock
adopt_lock
但是还有一些额外的功能:
如:
1. 同时获取多个锁
mutex m1,m2;
{
// 希望同时获取m1和m2,要么都获取成功,要么都不成功
// 创建之后,实际上没有获取锁
std::unique_lock<std::mutex> lock1{m1, std::defer_lock};
std::unique_lock<std::mutex> lock2{m2, std::defer_lock};
// 同时获取lock1和lock2表示的两个锁
std::lock(lock1, lock2); // 同时获取
// ......临界区代码
}
2. 管理锁住的粒度(操作共享资源的代码块的大小)
void add_x1()
{
int c = 100000;
// explicit unique_lock( mutex_type & m);
// 没加参数表示构造的时候获取锁
std::unique_lock<std::mutex> lock{m}; // 构造的时候上锁
while (c--)
{
x++;
lock.unlock(); // 手动释放锁
scanf("......");
lock.lock(); // 手动加锁
}
}
03 线程条件变量
线程可以等待一个程序员人为设置的一个"条件"
条件不满足的时候,线程可以等待这个条件,当条件满足的时候,线程可以继续往下运行
定义于头文件 <condition_variable>
class condition_variable
能够阻塞一个线程,或者同时阻塞多个线程,直到条件变量表示的"条件"成立,并且被其他地方通知
======>用于"生产者-消费者"模式
cpp#include <iostream> #include <thread> #include <mutex> #include <condition_variable> using namespace std; mutex m; // 全局的锁 condition_variable cond; // 条件变量,表示程序员抽象出来的一个条件 // 全局的共享资源 int x = 0; // 生产者函数 void add_x() { while (1) { // while (!m.try_lock()); // 上锁 m.lock(); x++; // 生产数据 cout << "生产者:" << x << endl; if (x > 5) { // 表示满足消费条件,应该通知消费者消费数据 cout << "通知消费者!" << endl; cond.notify_all(); // 通知所有等待的线程 } m.unlock(); this_thread::sleep_for(1s); } } // 生产者函数 // void add_x() { // while (1) { // // while (!m.try_lock()); // 上锁 // m.lock(); // x++; // 生产数据 // cout << "生产者:" << x << endl; // m.unlock(); // this_thread::sleep_for(1s); // } // } // 消费者函数 void delete_x() { while (1) { // 没加参数表示构造的时候获取锁 unique_lock<mutex> lk{m}; // 构造的时候上锁 if (x > 5) { x = x - 5; // 消费数据 cout << "消费者:" << x << endl; } else { cout << "我在浪费CPU!" << endl; cout << "等待一个条件" << endl; cond.wait(lk); // 原子的解锁lock,阻塞等待cond表示的条件 } } } // 消费者函数 // void delete_x() { // while (1) { // // while (!m.try_lock()); // m.lock(); // if (x >= 5) { // x = x - 5; // 消费数据 // cout << "消费者:" << x << endl; // } else { // cout << "我在浪费CPU!" << endl; // } // m.unlock(); // this_thread::sleep_for(1s); // } // } int main() { // 实例化一个线程对象,自动开始运行 thread t1{add_x}; thread t2{delete_x}; // 等待线程结束 t1.join(); // 阻塞 t2.join(); // 阻塞 return 0; }