进程与线程
进程:直观的说就是任务管理器中各种正在运行的程序。对于操作系统来说,进程仅仅是一个数据结构,并不会真实的执行代码
线程:通常被称作但并不真的是轻量级进程或实际工作中的进程,它会真实的执行代码。每个线程都有一个需要执行的代码块称为线程回调函数。每个进程启动的时候会同步启动一个主线程,而主线程所执行的代码块就是main函数。当main函数结束时,主线程结束并销毁,同时其他子线程随之销毁
真并发与伪并发
伪并发
在早期的cpu即单核cpu中,因性能核心各方面较为落后,并发编程实际是一个伪并发编程,即系统中所有进程按照优先级去抢占cpu时间片,也就是系统一会执行这个一会执行哪个。
由于抢占时间片所需时间较短,所以我们并不觉得程序卡顿。但各进程抢占cup时间片是一个很麻烦的事情,cpu虽然提供任务切换的功能即TSS任务段,但Windows并不使用。这是因为Windows自己实现了线程调度,即在线程切换时,上个线程代码执行到的地方的线程的状态,线程上下文,通用寄存器,段寄存器,硬件调试寄存器,EIP(指令指针寄存器),EFLAGS等都会被Windows通过Windows(Context)保存,直到再次切换回来后再加载
真并发
随着科技的发展,cpu由单核cpu变成了多核cpu。此时多个核心可以同时独立执行一个 任务,此时也称作真并发
并发形式
1.多进程并发:一个进程里只有一个线程,同时启动多个进程实现并发,如浏览器打开的多个窗口
2.多线程并发:一个进程内运行多个线程,是真实的并发。其中存在变量的访问问题,具体如下:有Value = 100 全局变量以及A,B两个线程。初始时A,B线程访问Value,访问值都是100,现AB两线程都对Value进行++。但操作完成后,Value的值为101,丢失了一个操作。这种情况叫做线程同步问题
线程的生存周期
1.当该线程回调函数执行完毕时,自然死亡
2.当主线程死亡时,子线程被动死亡
并发与并行:并发更强调数量,并行更强调性能
线程应用
普通函数应用
cpp
#include<iostream>
#include<thread>
void FirstThreadCallBack() //构建一个普通函数作为子线程
{
for (size_t i = 0; i < 100000; i++)
{
std::cout << "First:" << i << std::endl;
}
}
int main()
{
std::thread obj(FirstThreadCallBack); //声明线程对象,启动一个线程去执行线程回调函数
for (size_t i = 0; i < 100000; i++)
{
std::cout << "main:"<< i << std::endl;
}
system("pause");//加上此函数使主线程不会结束,让我们更清晰看到线程并发的过程。否则主线程结束子线程随之结束
return 0;
}
此时程序会同时进行上述两个循环打印
仿函数应用
cpp
#include<iostream>
#include<thread>
class Exec//一个仿函数
{
public:
void operator()()const
{
std::cout << "Exec" << std::endl;
}
};
int main()
{
Exec e;
std::thread obj(e);
return 0;
}
此时打印Exec
Lambda应用
cpp
#include<iostream>
#include<thread>
int main()
{
std::thread obj([] {std::cout << "Lambda" << std::endl; });
return 0;
}
此时程打印Lambda
综上可知,任何可以调用的类型都可以用于线程对象的构造函数传参
线程死亡
一旦线程启动了,我们就需要知道线程是怎么结束的
1.自然死亡:thread析构函数terminate()在子线程执行完毕后析构子线程
2.非自然死亡:thread析构函数执行完毕时,子线程析构,但子线程并没有执行完毕
3.等待:绝对的自然死亡 等待子线程执行完毕后,程序再进行执行
4.不再等待:主线程存活时后台运行,依赖于主线程的存活
5.如果一个线程是Windows原生线程,主线程销毁后其也会死亡
Windows原生线程
现在我们验证一下,当主线程死亡时,Windows原生线程会不会死亡
cpp
#include<iostream>
#include<thread>
#include<windows.h>
DWORD ThreadCallBack(LPVOID lpThreadParameter)
{
for (size_t i = 0; i < 100000; i++)
{
std::cout << "First:" << i << std::endl;
}
return 0;
}
int main()
{
CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)ThreadCallBack, NULL, NULL, NULL);//创建了一个windows原生线程
return 0;
}
此时运行程序,发现随着主线程的结束该Windows原生线程死亡
等待死亡
cpp
#include<iostream>
#include<thread>
#include<windows.h>
DWORD ThreadCallBack(LPVOID lpThreadParameter)
{
for (size_t i = 0; i < 100000; i++)
{
std::cout << "First:" << i << std::endl;
}
return 0;
}
int main()
{
HANDLE hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)ThreadCallBack, NULL, NULL, NULL);//创建一个原生的Windows线程
WaitForSingleObject(hThread, -1); //此时主线程会永久等待该子线程结束以后再结束
return 0;
}
此时运行程序原生线程不会死亡,直到它运行完毕
阻塞等待
cpp
#include<iostream>
#include<thread>
#include<windows.h>
DWORD ThreadCallBack(LPVOID lpThreadParameter)
{
for (size_t i = 0; i < 100000; i++)
{
std::cout << "First:" << i << std::endl;
}
return 0;
}
int main()
{
std::thread obj(FirstThreadCallBack);//创建一个普通的线程
obj.join(); //阻塞等待,作用是在此处等待子线程结束,程序再继续运行。
//当使用此函数时,我们通常需要加一个异常处理。这是因为子线程可能会出现一个异常报错而导致无法执行完毕以至于程序一直处于阻塞等待的情况
return 0;
}
此时运行程序,知道子线程运行完毕,主线程才会结束
不再等待
cpp
#include<iostream>
#include<thread>
#include<windows.h>
DWORD ThreadCallBack(LPVOID lpThreadParameter)
{
for (size_t i = 0; i < 100000; i++)
{
std::cout << "First:" << i << std::endl;
}
return 0;
}
int main()
{
std::thread obj(FirstThreadCallBack);
obj.detach(); //不再等待:同Windows原生线程一样,主线程死亡,其子线程也死亡
//此时额外加一个循环,程序在执行该循环时,主线程没有死亡,子线程也不会死亡,而是一起执行两个线程
for (size_t i = 0; i < 100000; i++)
{
std::cout << "main:" << i << std::endl;
}
return 0;
}
线程同步问题
问题演示
如下当我们演示一个简单的线程同步
cpp
#include<iostream>
#include<thread>
#include<windows.h>
#include<string.h>
void Print(std::string szBuffer,int nCount)
{
for (size_t i = 0; i < nCount; i++)
{
std::cout << szBuffer << ":" << i << std::endl;
}
}
int main()
{
std::thread obj(Print,"abc",200);
system("pause");
return 0;
}
程序运行发现:
原因:这就是时间切片的伪并发可能出现的问题,很形象展示了线程同步问题这个现象
现我们针对如下线程同步程序进行进一步的问题解决讲解
cpp
#include <iostream>
#include <thread>
int g_Value = 0;
void add()
{
for (size_t i = 0; i < 1000000; i++)
{
g_Value++;
}
}
int main()
{
std::thread objA(add);
std::thread objB(add);
objA.join();
objB.join();
std::cout << g_Value << std::endl;
system("pause");
return 0;
}
程序运行以后,g_Value的最终结果应该是2000000,但但每次运行时g_Value都是随机数,这是因为在线程同步时出现丢失操作
互斥体解决线程同步问题
方法一:使用互斥体方法
cpp
#include <iostream>
#include <thread>
#include<mutex>
int g_Value = 0;
std::mutex some_mutex; //声明一个互斥体,用于线程可能出错的地方
void add()
{
for (size_t i = 0; i < 1000000; i++)
{
some_mutex.lock(); //该函数被互斥体加锁保护。当一个线程在访问该函数时,其他线程无法访问
g_Value++;
some_mutex.unlocke(); //互斥体解锁
}
}//此时该函数不会再出现多线程同时访问的问题了
int main()
{
std::thread objA(add);
std::thread objB(add);
objA.join();
objB.join();
std::cout << g_Value << std::endl;
system("pause");
return 0;
}
方法二:使用锁类模板
cpp
#include <iostream>
#include <thread>
#include<mutex>
int g_Value = 0;
void add()
{
for (size_t i = 0; i < 1000000; i++)
{
//构造函数调用时加锁,析构函数调用时解锁
std::lock_guard<std::mutex> guard(some_mutex);
g_Value++;
}
}
int main()
{
std::thread objA(add);
std::thread objB(add);
objA.join();
objB.join();
std::cout << g_Value << std::endl;
system("pause");
return 0;
}
以上两种方法可以很好的解决线程同步问题
作业
01.尝试使用多线程造成线程同步问题。
02.尝试使用thread库中的其他控制函数