c++多线程

程序在本质上通常以文件形式存储于磁盘中,当程序运行时,磁盘中的数据会被加载到内存中,内存的读写速度远高于硬盘,然而一旦断电,内存中的数据便会消失,内存主要承担数据存储的功能,不具备计算能力,数据从内存被传送到 CPU 进行处理,CPU内部虽有存储空间,如寄存器等,但容量相对较小

CPU 执行指令时通常是逐一处理,比如 “g_n = g_n + 1;” 这行代码,在编译后可能会生成多个指令,mov eax, [g_n],将变量 g_n 的值加载到寄存器 eax 中,add eax, 1,将寄存器 eax 中的值加 1,mov [g_n], eax,将寄存器 eax 中的值存回变量 g_n。在指令执行过程中,随时可能被操作系统暂停,当 CPU 完成计算后,结果会被返回到内存中。

我们当前所使用的是分时系统,在分时系统中,系统会为每个线程分配一定的时间片,比如 50ms 或者其他时长,当时间片耗尽,该线程会被暂停,系统转而执行下一个程序,系统通过这种时间切片的方式轮流执行不同的线程,从而给人一种多线程同时进行的错觉

下述用例中,会出现多个线程重复计算的问题,第一个线程刚处理完计算,结果还没返回到内存中,可能就被系统暂停了, 第二个线程计算的时候,可能用的是内存中的旧值,也有可能已经算到50了,第三个线程接着计算的时候,可能用的还是1,这就是多线程共享数据,每个线程都可以更改数据,但由于分时系统的特点,会导致数据出现不确定的变化

#include <iostream>
using namespace std;
#include <thread>
int g_n = 0; 
void test() {
	for (int i = 0; i < 10000; ++i)
	{
		g_n = g_n + 1;
	}
}
int main() {
	thread t1(test);
	thread t2(test);
	thread t3(test);
	t1.join();
	t2.join();
	t3.join();
	cout << g_n << endl;
}

引入互斥对象,对共享资源加以保护

#include <iostream>
using namespace std;
#include <thread>
#include <mutex>
int g_n = 0; 
mutex m;
void test() {
	for (int i = 0; i < 1000000; ++i)
	{
		m.lock();
		g_n = g_n + 1;
		m.unlock();
	}
}
int main() {
	thread t1(test);
	thread t2(test);
	thread t3(test);
	t1.join();
	t2.join();
	t3.join();
	cout << g_n << endl;
}

在共享资源前后加lock和unlock,获得lock的线程,可以访问共享资源,其他线程只能等待,释放锁之后,等待的线程可以获得锁,从而读写共享资源,实现多线程同步

如果只lock,忘记了unlock,会死锁,等待的线程会一直等待,直至系统资源耗完,程序崩溃

引入lock_guard模板类,lock_guard对象在离开作用域时,会自动unlock

#include <iostream>
using namespace std;
#include <thread>
#include <mutex>
int g_n = 0; 
mutex m;
void test() {
	for (int i = 0; i < 1000000; ++i)
	{
		lock_guard<mutex> l_g(m);
		g_n = g_n + 1;
	}
}
int main() {
	thread t1(test);
	thread t2(test);
	thread t3(test);
	t1.join();
	t2.join();
	t3.join();
	cout << g_n << endl;
}

创建lock_guard<mutex>类型的对象l_g,用m初始化的时候,构造函数中会获取锁,l_g离开作用域销毁时,析构函数中会自动释放锁

互斥锁需要两个变量,一个变量保存数据,一个变量负责锁,效率不高,引入原子操作

#include <iostream>
#include <thread>
#include <mutex>
#include <atomic>
using namespace std;
atomic_int g_n = 0; //只需要定义一个变量,存储数据,而且本身具备多线程互斥效果
void test() {
	for (int i = 0; i < 1000000; ++i)
	{
		++g_n;
	}
}
int main() {
	thread t1(test);
	thread t2(test);
	thread t3(test);
	t1.join();
	t2.join();
	t3.join();
	cout << g_n << endl;
}

原子操作:要么完全执行,要么完全不执行,++g_n编译后的指令是

mov eax, dword ptr [g_n]         //从内存中拿数据的指令
add eax, 1                                //cpu执行计算的指令
mov dword ptr [g_n], eax         //将结果返回到内存中的指令

完全执行,就是把这三条指令都完成,内存中已更新为最新的值,当前线程在对共享资源写操作过程中,别的线程不会同时对共享资源读写操作

条件变量,满足条件,释放阻塞,不满足条件,阻塞,通过控制条件来控制不同的线程同步

#include <list>
#include <string>
#include <thread>
#include <iostream>
using namespace std;
#include<mutex>
#include <condition_variable>

list<string> g_task;
mutex m;  
condition_variable cv; 
void workThread() {
	cout << "工作线程启动" << endl;
	unique_lock<mutex> lk(m); // 
    cv.wait(lk);
	while (!g_task.empty())
	{
		cout << "处理:" << g_task.front() << endl;
		g_task.pop_front();
	}
	cout << "工作线程结束" << endl;
}
int main() {
	thread t1(workThread);

	for (int i = 0; i < 3; ++i) {
		string str;
		cin >> str;
		g_task.push_back(str);
	}
	cout << "数据生产完成,开理处理\n";
	cv.notify_one(); //唤醒一个正在等待的线程,当某个条件得到满足时,调用 notify_one 将会通知其中一个线程,并唤醒它,让它开始执行,cv.notify_all()被用来唤醒所有正在等待的线程。这样可以确保在多个线程中选择一个线程继续执行

	t1.join();
	cout << "数据处理完成,程序结束\n";
	return 0;
}

创建了一个 unique_lock<mutex>类型的对象lk,并对互斥量m进行加锁,保护共享资源g_task,确保其他线程不会在同一时间修改共享数据,而且unique_lock可以自动释放锁和获取锁,可以跟条件变量的wait函数完美配合
cv.wait 将当前线程放入等待阻塞状态,直到另一个线程调用 notify_* 函数为止,在等待期间,互斥锁会被自动释放,允许其他线程对共享数据进行修改,当等待结束时,wait 函数将再次对互斥量进行加锁,保护共享资源

用条件变量实现三个线程的同步,线程1输出A,线程2输出B,线程3输出C,控制三个线程输出ABC,输出8组

#include <iostream>
#include <mutex>
#include <condition_variable>
#include <thread>
using namespace std;

condition_variable cv;
mutex m;
int curentThread = 1;
void printfChar(const char ch,int threadNum,int count) {
	for (int i = 0; i < count;++i) {
		unique_lock<mutex> ul(m);
		cv.wait(ul, [threadNum] {return threadNum == curentThread; });
		cout << ch;
		curentThread = (curentThread % 3) + 1;
		cv.notify_all();
	}
}
int main() {
	thread t1(printfChar,'A',1,8);
	thread t2(printfChar,'B',2,8);
	thread t3(printfChar,'C',3,8);
	t1.join();
	t2.join();
	t3.join();
	return 0;
}

创建三个线程,分别传入要打印的字符,线程号,循环次数,全局变量当前线程号初始化为1,如果是线程2或者线程3先执行printfChar函数,先获得锁,别的线程会被阻塞,然后执行wait函数的时候,条件不满足,wait函数将当前线程阻塞,并释放锁,当线程1执行printfChar函数时,获得锁,执行wait函数,条件满足,释放锁,继续执行到notify_all,唤醒所有线程,线程2和线程3执行wait,再检查条件,不满足继续阻塞,满足,往下执行。条件是按照线程号1 2 3轮替,对应的线程按照1 2 3执行,依次打印出ABC

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

码成行

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

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

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

打赏作者

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

抵扣说明:

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

余额充值