1.thread的简单使用
#include <iostream>
#include <thread>
#include <Windows.h>
using namespace std;
void thread01()
{
for (int i = 0; i < 5; i++)
{
cout << "Thread 01 is working !" << endl;
Sleep(100);
}
}
void thread02()
{
for (int i = 0; i < 5; i++)
{
cout << "Thread 02 is working !" << endl;
Sleep(200);
}
}
int main()
{
thread task01(thread01);
//创建一个thread类对象task01,以thread01做为一个线程创建时构造的参数,创建的线程01执行的是thread01函数中的任务
thread task02(thread02);
task01.join();
//将01子线程加入,阻塞主线程,等到01运行完才进行主线程
task02.join();
//将02子线程加入,阻塞主线程,等到02运行完才进行主线程
for (int i = 0; i < 5; i++)
{
cout << "Main thread is working !" << endl;
Sleep(200);
}
system("pause");
}
子线程01,02不会互相阻塞对方,当这两个子线程都结束时才会继续主线程
2.线程同步
#include <iostream>
#include <thread>
#include <Windows.h>
using namespace std;
int a = 0;
void thread01()
{
for (int i = 0; i < 10000000; i++)
a++;
}
int main()
{
thread task01(thread01);//以thread01做为一个线程创建时的参数,创建的线程01执行的是thread01函数中的任务
for (int i = 0; i < 10000000; i++)
a++;
task01.join();//将01子线程加入,阻塞主线程,等到01运行完才进行主线程
cout << a << endl;
system("pause");
}
将i 设置到10000000时会出现错误结果,一次for循环的时间大于两条语句之间的间隔,可以看到,代码中全局变量i先在主线程中自加了10000000次,再在主线程中自加了10000000次,结果应是20000000,但显示的结果为13010045,推测是主线程进行了3010045次还未结束时子线程就加进来了阻塞了主线程然后执行子线程,于是将子进程设置为空函数,预计最后显示的结果为3010045
可以看到结果并不是我们的猜想,说明子进程的加入并不会阻塞正在进行的主线程中的操作
我们查看子进程中a++的反汇编:
a++;
00D02643 mov eax,dword ptr [a (0D0E2D4h)]
//将内存中地址为0D0E2D4h的值mov给寄存器eax
00D02648 add eax,1
//将eaxadd1
00D0264B mov dword ptr [a (0D0E2D4h)],eax
//将eax的值放回 0D0E2D4h中
00D02650 jmp thread01+31h (0D02631h)
C++为高级语言,一句自增操作在翻译成汇编语言后变成了四句
再查看主进程中a++的反汇编:
00D03C39 mov eax,dword ptr [a (0D0E2D4h)]
00D03C3E add eax,1
00D03C41 mov dword ptr [a (0D0E2D4h)],eax
00D03C46 jmp std::thread::join+47h (0D03C27h)
可以看到a存放的寄存器的地址相同,但指令存放的地址不同。
并发为交替地进行两个以下的操作,假设每个操作执行X毫秒
当子进程加进来后,比如在执行进程1X毫秒后只执行了第一句,假设此时a为5000,然后去执行进程2执行X毫秒a自增了3000,再切换回进程1接着执行之前没有执行完的第二局,a变回5000,在进程2中自增的3000消失掉了。
这就是同步带来的问题
3.线程同步的方法
原子操作:是指线程在访问资源时确保其他线程不会访问相同的资源(让C++一行代码对应的多行汇编代码视作一个整体,要么一起执行完毕,要不一行都不执行)
for (int i = 0; i < 10000000; i++)
{
InterlockedAdd((long*)&a, 1);
}
但这仅限于加法,泛化性不高
因此引入锁这个概念
在使用前先创建临界区对象,相当于去买锁
CRITICAL_SECTION g_cs;
要先初始化锁InitializeCriticalSection,使用结束了要删掉锁DeleteCriticalSection,使用锁前EnterCriticalSection加锁,使用后解锁LeaveCriticalSection;
#include <iostream>
#include <thread>
#include <Windows.h>
using namespace std;
int a = 0;
//创建临界区对象--等价于锁
CRITICAL_SECTION g_cs;
void thread01()
{
for (int i = 0; i < 10000000; i++)
{
//进来时上锁
EnterCriticalSection(&g_cs);
a++;
//出去解锁
LeaveCriticalSection(&g_cs);
}
}
int main()
{
InitializeCriticalSection(&g_cs);
thread task01(thread01);
for (int i = 0; i < 10000000; i++)
{
//进来时上锁
EnterCriticalSection(&g_cs);
a++;
//出去解锁
LeaveCriticalSection(&g_cs);
}
task01.join();
cout << a << endl;
system("pause");
//不使用时删掉该锁
DeleteCriticalSection(&g_cs);
}