Thread 线程库
线程的创建
void funa()
{
cout << "funa()" << endl;
}
void funb(int a)
{
cout << "funb(int a)" << endl;
}
void func(int& a)
{
a += 10;
cout << "func(int& a)" << endl;
}
void fund(int* p)
{
if (p == nullptr) return;
*p += 100;
cout << "fund(int* p)" << endl;
}
int main()
{
int x = 10;
std::thread tha(funa);
std::thread thb(funb, x);
std::thread thc(func, std::ref(x));//需要引用不能直接给,需要ref告诉其为引用类型
std::thread thd(fund, &x);
tha.join(); //等待其他线程结束
thb.join();
thc.join();
thd.join();
cout << x << endl;
return 0;
}
detach 句柄独立
容许线程从线程句柄独立开来执行
int main()
{
std::thread tha(funa);
tha.detach(); //主线程与开辟线程没有关系,主线程可以首先结束
cout << "main end" << endl;
return 0;
}
主线程结束会将资源剥夺,所有尽量不要进行该操作
线程资源转移
int main()
{
std::thread tha(funa);
std::thread thb(std::move(tha));
//std::thread thb(tha); error!!!
//thb = tha;
std::thread thc;
thc = std::move(thb);
thc.join();
return 0;
}
不能通过拷贝构造与赋值语句进行线程之间的转换,只能使用移动构造以及移动赋值进行资源转移
sleep_ for
使当前线程的执行停止指定的时间段
int funa(int n,int &x )
{
for (int i = 1; i <= n; ++i)
{
cout << "thread funa" << endl;
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
x = n;
cout << "thread funa end" << endl;
return n;
}
//C++无法获得线程的返回值
int main()
{
int a = 0; //但是可以通过引用的方式进行获取
thread tha(funa, 5, std::ref(a));
tha.join();
cout << "main end" << endl;
return 0;
}
int funa(int n,int &x )
{
for (int i = 1; i <= n; ++i)
{
cout << "thread funa" << endl;
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
x = n;
cout << "thread funa end" << endl;
return n;
}
//C++无法获得线程的返回值
int main()
{
int a = 0; //但是可以通过引用的方式进行获取
thread tha(funa, 5, std::ref(a));
tha.join();
tha.detach();
cout << "main end" << endl;
return 0;
}
这样会导致,主线程与子线程分离,子线程根本无法结束
全局函数线程化
class Object
{
private:
int value;
public:
Object(int x = 0):value(x){}
~Object() {}
void run()
{
cout << "Object run" << endl;
}
};
int main()
{
Object obj;
std::thread thobj(&Object::run, &obj);
thobj.join();
cout << "main end" << endl;
return 0;
}
Object::run 线程调用,需要加上&符号,并且传入一个this指针
静态全局函数
class Object
{
private:
int value;
public:
Object(int x = 0):value(x){}
~Object() {}
void run()
{
cout << "Object run" << endl;
}
static void fun(int x )
{
Object obj(x);
obj.run();
cout << "static fun" << endl;
}
};
int main()
{
std::thread tha(Object::fun, 5);
tha.join();
cout << endl;
return 0;
}
静态成员函数,线程调用不需要传参this指针,并且可以通过线程调动静态成员的方式去另向调动普通成员函数
joinable
检查线程是否可合并,即潜在地运行于平行环境中,也就是线程是否于某一个函数资源进行关联
void foo()
{
std::this_thread::sleep_for(std::chrono::seconds(1));
cout << "foo end" << endl;
}
int main()
{
std::thread t;
cout << "befor starting,joinable" << t.joinable() << endl;
t = std::thread(foo); //无名对象线程 移动赋值
cout << "after starting,joinable" << t.joinable() << endl;
t.join();
cout << "after joining" << t.joinable() << endl;
}
hardware_concurrency
int main()
{
cout << t.hardware_concurrency() << endl;//返回CPU内核数
cout << std::thread::hardware_concurrency() << endl;
}
返回CPU当前逻辑处理数
线程的互斥
int g_num = 0;
void print(int id)
{
for (int i = 0; i < 5; ++i)
{
++g_num;
cout << "id:" << id << "==>" << g_num << endl;
std::this_thread::sleep_for(std::chrono::seconds(2));
}
}
int main()
{
std::thread tha(print, 0);
std::thread thb(print, 1);
tha.join();
thb.join();
cout << "main end" << endl;
}
非原子操作的每次执行并不一定执行结果相同
原子操作定义
我们通过原子操作进行定义g_num
std::atomic_int g_num = 0;
可以保证每次结果相同
异变关键字不能解决原子操作的问题
互斥锁
int g_num = 0;
std::mutex mtx;
void print(int id)
{
for (int i = 0; i < 5; ++i)
{
mtx.lock();
++g_num;
cout << "id:" << id << "==>" << g_num << endl;
mtx.unlock();
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
int main()
{
std::thread tha(print, 0);
std::thread thb(print, 1);
tha.join();
thb.join();
cout << "main end" << endl;
}
互斥锁只要解决异步操作中对同一个资源的竞争
try_lock
void print(int id)
{
for (int i = 0; i < 5; ++i)
{
while (!mtx.try_lock()) //试图加锁,加锁失败也会执行
{ //加锁失败则一直循环
cout << "lock..." << endl;
}
++g_num;
cout << "id:" << id << "==>" << g_num << endl;
mtx.unlock();
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
try_lock 加锁成功会返回1,失败返回0,倘若加锁失败它依旧会向下执行,所以一般使用会将其于while循环嵌套,则该锁会在无法获得加锁的情况下不断地尝试加锁
recursive_mutex 递归锁
递归锁,提供能被同一线程递归锁定的互斥设施
如上图,当我们进入线程,去调动第一个函数进行加锁,随机递归进入第二个线程,继而又进行了加锁,而普通锁进行两次加锁则会出现错误
而当我们使用了递归锁,则在递归过程中再次遇到加锁的时候,则该锁失效直接进入函数,并且每遇到一次锁都会又一次相应的解锁
lock_guard
实现严格基于作用域的互斥体所有权包装器
void print(int id)
{
for (int i = 0; i < 5; ++i)
{
std::lock_guard<std::mutex> lock(mtx); //对象构造 内部调用互斥锁
++g_num;
cout << id << "-->" << g_num << endl;
std::this_thread::sleep_for(std::chrono::milliseconds(200));
}
}
为在作用域块期间占用互斥提供便利RAII风格机制,与智能指针相似
而在上面代码中,作用域块就是for语句,则当for语句结束,该对象进行析构继而就进行解锁
条件变量 condition_variable
condition_variable 类是同步原语,能用于阻塞一个线程,或同时阻塞多个线程,直至另一线程修改共享变量,并通知condition_variable
std::mutex mx; //互斥量
std::condition_variable cv; //条件变量
int isReady = 0; //全局变量
void print_A()
{
std::unique_lock<std::mutex> lock(mx);
int i = 0;
while (i < 10)
{
while (isReady != 0)
{
cv.wait(lock);//等待
}
cout << "A" << endl;
isReady = 1;
++i;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
cv.notify_all(); //唤醒所有等待线程
}
}
void print_B()
{
std::unique_lock<std::mutex> lock(mx);
int i = 0;
while (i < 10)
{
while (isReady != 1)
{
cv.wait(lock);//等待
}
cout << "B" << endl;
isReady = 2;
++i;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
cv.notify_all(); //唤醒所有等待线程
}
}
void print_C()
{
std::unique_lock<std::mutex> lock(mx);
int i = 0;
while (i < 10)
{
while (isReady != 2)
{
cv.wait(lock);//等待
}
cout << "C" << endl;
isReady = 0;
++i;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
cv.notify_all(); //唤醒所有等待线程
}
}
int main()
{
thread tha(print_A);
thread thb(print_B);
thread thc(print_C);
tha.join();
thb.join();
thc.join();
}
这样就可以达成,ABC不断循环一个一个打印
当B,C线程进入调用函数,在条件变量不符合情况下,进入wait队列,线程阻塞并且将锁状态释放
直到A线程进入函数,并且由于BC线程在阻塞后将锁状态释放,A线程获得互斥锁,接着判断条件变量符合,不执行等待,进行打印,接着唤醒在等待队列上的所有线程
BC线程被唤醒,C回到被阻塞位置继续执行,并且获得锁,接着由于条件变量不符,又回到了阻塞状态
B线程回到被阻塞位置,获得锁,条件变量符合进行打印,唤醒在等待队列的所有线程
惊群现象
当我们的阻塞队列中拥有较多线程的时候,当我们进行唤醒的时候,将所有阻塞队列的线程都进行唤醒,并且当互斥锁释放后,被唤醒的所有线程都开始抢占这个互斥锁,但是仅仅只有一个线程可以抢占,其他的则又进行等待
notify_one 则是一次仅能唤醒一个线程用来避免惊群现象
条件变量判断
void print_A()
{
std::unique_lock<std::mutex> lock(mx);
int i = 0;
while (i < 10)
{
if (isReady != 0)
{
cv.wait(lock);//等待
}
cout << "A" << endl;
isReady = 1;
++i;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
cv.notify_all(); //唤醒所有等待线程
}
}
若我们将原本第二个while修改为if判断,那么假如当A线程唤醒等待线程并且释放锁,BC线程在进入到原先位置继续运行,则会直接进入下一步进行打印,就会导致不能严格按照ABC顺序进行打印
void print_A()
{
std::unique_lock<std::mutex> lock(mx);
int i = 0;
while (i < 10)
{
while (isReady != 0)
{
cv.wait(lock);//等待
}
cout << "A" << endl;
isReady = 1;
++i;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
cv.notify_one();
}
}
若我们将原先唤醒所有线程修改为仅仅唤醒一个线程,那么会导致若我们需要下一次执行B线程,而C线程先抢占到了互斥锁,那么会导致C再次回到等待队列,C线程无法去执行唤醒操作,导致所有线程都进入到了阻塞队列
unique_lock 可移植互斥锁
std::mutx mx;
std::condition_variable cv;
int number = 1;
void Print_A()
{
unique_lock<std::mutex> lock(mx);
unique_lock<std::mutex> lc(std::move(lock));
}
可以将锁的拥有权进行转移