随笔 - 9  文章 - 0  评论 - 0  阅读 - 659

C++复习之(并发多线程)

1.多线程好处

   提高cpu利用率,若只有一个线程,当这个线程阻塞后整个活动停止;若有多个线程分别完成不同的任务,当一个线程被阻塞,另外的线程也能继续工作,从而提高cpu的利用率。 当性能的增幅比不上成本时,多线程就没有那么好了。运行越多的线程,操作系统需要为每个线程分配独立的栈空间,需要越多的上下文切换,这会消耗很多操作系统资源,如果在线程上的任务完成得很快,那 么实际执行任务的时间要比启动线程的时间小很多,所以在某些时候,增加一个额外的线程实际上会降低,而非提高应用程序的整体性能,此时收益就比不上成本。

2.线程的创建

  使用std::thread对象来创建一个线程,并传入该线程需要执行的函数。在主函数中,使用join()函数来等待线程结束,代码例子如下。

线程创建与回收

#include<iostream>
#include<thread>
using namespace std;
void proc(int &a)
{
    cout << "我是子线程,传入参数为" << a << endl;
    cout << "子线程中显示子线程id为" << this_thread::get_id()<< endl;
}
int main()
{
    cout << "我是主线程" << endl;
    int a = 9;
    thread th2(proc,ref(a));//第一个参数为函数名,第二个参数为该函数的第一个参数,如果该函数接收多个参数就依次写在后面。此时线程开始执行。
    cout << "主线程中显示子线程id为" << th2.get_id() << endl;
    th2.join();//此时主线程被阻塞直至子线程执行结束。
    return 0;
}

3. 互斥锁的使用

3.1 互斥锁作用与临界区、信号量区别

  互斥锁目的保证线程安全和防止死锁。当一个线程在使用某个互斥资源,需要对资源上锁,从而解决访问冲突。当两个变量在使用同一个共享变量时,可能会发生数据混乱,从而发生程序错误。

 临界区:速度最快,但只能作用于同一进程下不同线程,不能作用于不同进程;临界区可确保某一代码段同一时刻只被一个线程执行;

  信号量:多个线程同一时刻访问共享资源,进行线程的计数,确保同时访问资源的线程数目不超过上限,当访问数超过上限后,不发出信号量;

  互斥锁:比临界区慢,但支持不同进程间的同步与互斥,互斥就是保证资源同一时刻只能被一个进程使用;

3.2 读写锁的使用

  创建一个shared_mutex对象,然后使用lock_shared(),unlock_shared()加读锁,lock(),unlock()加写锁。可以多个同时读,但只能一个同时写。代码如下:

读写锁

shared_mutex s_m;

std::string book;

void read()
{
	s_m.lock_shared();
	cout << book;
	s_m.unlock_shared();
}

void write()
{
	s_m.lock();
	book = "new context";
	s_m.unlock();
}

3.3 自旋锁

  如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,而不是阻塞线程,这减少了线程的上下文切换,节约内核资源,但同时会浪费cpu资源,因此自旋锁适合锁使用者保持锁时间比较短的情况。

3.4 lock_guard 和 unique_lock

  直接使用lock()但忘记unlock(),会导致锁无法被释放,使用lock_guard()unique_lock()则可以避免这个问题。

  原理:声明一个局部的std::lock_guard对象,在其构造函数中进行加锁,在其析构函数中进行解锁。创建即加锁,作用域结束自动解锁。从而使用std::lock_guard()就可以替代lock()unlock()通过使用{}来调整作用域范围,可使得互斥量m在合适的地方被解锁。

lock_guard

#include<iostream>
#include<thread>
#include<mutex>
using namespace std;
mutex m;//实例化m对象,不要理解为定义变量
void proc1(int a)
{
    lock_guard<mutex> g1(m);//用此语句替换了m.lock();lock_guard传入一个参数时,该参数为互斥量,此时调用了lock_guard的构造函数,申请锁定m
    cout << "proc1函数正在改写a" << endl;
    cout << "原始a为" << a << endl;
    cout << "现在a为" << a + 2 << endl;
}//此时不需要写m.unlock(),g1出了作用域被释放,自动调用析构函数,于是m被解锁

void proc2(int a)
{
    {
        lock_guard<mutex> g2(m);
        cout << "proc2函数正在改写a" << endl;
        cout << "原始a为" << a << endl;
        cout << "现在a为" << a + 1 << endl;
    }//通过使用{}来调整作用域范围,可使得m在合适的地方被解锁
    cout << "作用域外的内容3" << endl;
    cout << "作用域外的内容4" << endl;
    cout << "作用域外的内容5" << endl;
}
int main()
{
    int a = 0;
    thread t1(proc1, a);
    thread t2(proc2, a);
    t1.join();
    t2.join();
    return 0;
}

  unique_lock()除了lock_guard()所有功能外,还有try_to_lock()defer_lock()try_to_lock():会尝试去锁定,得保证锁处于unlock的状态,然后尝试现在能不能获得锁;尝试用mutx的lock()去锁定这个mutex,但如果没有锁定成功,会立即返回,不会阻塞在那里,并继续往下执行;defer_lock: 始化了一个没有加锁的mutex。

unique_lock

#include<iostream>
#include<thread>
#include<mutex>
using namespace std;
mutex m;
void proc1(int a)
{
	unique_lock<mutex> g1(m, defer_lock);//始化了一个没有加锁的mutex
	cout << "xxxxxxxx" << endl;
	g1.lock();//手动加锁,注意,不是m.lock();注意,不是m.lock(),m已经被g1接管了;
	cout << "proc1函数正在改写a" << endl;
	cout << "原始a为" << a << endl;
	cout << "现在a为" << a + 2 << endl;
	g1.unlock();//临时解锁
	cout << "xxxxx" << endl;
	g1.lock();
	cout << "xxxxxx" << endl;
}//自动解锁

void proc2(int a)
{
	unique_lock<mutex> g2(m, try_to_lock);//尝试加锁一次,但如果没有锁定成功,会立即返回,不会阻塞在那里,且不会再次尝试锁操作。
	if (g2.owns_lock()) {//锁成功
		cout << "proc2函数正在改写a" << endl;
		cout << "原始a为" << a << endl;
		cout << "现在a为" << a + 1 << endl;
	}
	else {//锁失败则执行这段语句
		cout << "" << endl;
	}
}//自动解锁

int main()
{
	int a = 0;
	thread t1(proc1, a);
	t1.join();
	//thread t2(proc2, a);
	//t2.join();
	return 0;
}

3.5  condition_variable

3.6 async和future

  std::async是一个函数模板,用来启动一个异步任务,它返回一个std::future类模板对象,future对象起到了占位的作用。future.get()只能调用一次。特别的shared_future.get()可以调用多次,因为他们一个转移值的所有权,一个是复制值。

async

#include <iostream>
#include <thread>
#include <mutex>
#include<future>
#include<Windows.h>
using namespace std;
double t1(const double a, const double b)
{
 double c = a + b;
 Sleep(3000);//假设t1函数是个复杂的计算过程,需要消耗3秒
 return c;
}

int main() 
{
 double a = 2.3;
 double b = 6.7;
 future<double> fu = async(t1, a, b);//创建异步线程线程,并将线程的执行结果用fu占位;
 cout << "正在进行计算" << endl;
 cout << "计算结果马上就准备好,请您耐心等待" << endl;
 cout << "计算结果:" << fu.get() << endl;//阻塞主线程,直至异步线程return
        //cout << "计算结果:" << fu.get() << endl;//取消该语句注释后运行会报错,因为future对象的get()方法只能调用一次。
 return 0;
}

3.7 atomic

   被atomic修饰的变量具有线程安全,不会有多个线程同时操作一个变量的问题。用来替代频繁加锁解锁问题。

 

posted on   俊男  阅读(109)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

点击右上角即可分享
微信分享提示