并发编程(一)线程基础知识与线程控制

进程与线程

进程:如任务管理器中各种程序叫做正在运行的进程。对于操作系统来说,仅仅是一个数据结构,并不真实的执行代码

线程:真实执行代码的

每个进程启动的是时候会同步启动一个主线程即main函数,当main函数结束时,该线程结束并销毁,同时其他线程随之销毁

线程都有一个需要执行的代码块称为线程回调函数

真并发与伪并发

真并发

当cpu是双核或者多核时,并不会一核心一任务,而是由单核心切换转为多核心切换,此时也称作真并发

伪并发

在早期的cpu即单核cpu中,因性能核心各方面较为落后,并发编程实际是一个伪并发编程,即系统中所有进程按照优先级去抢占cpu时间片,也就是系统一会执行这个一会执行哪个。

由于抢占时间片所需时间较短,所以我们并不觉得程序卡顿。但各进程抢占cup时间片是一个很麻烦的事情,cpu虽然提供任务切换的功能即TSS任务段,但Windows并不使用。

因为Windows实现了线程调度,即再线程切换时,上个线程代码执行到的地方的线程的状态,线程上下文,通用寄存器,段寄存器,硬件调试寄存器,EIP(指令指针寄存器),EFLAGS等都会被Windows通过Windows(Context)保存,直到再次切换回来后再加载

并发形式

  1. 多进程并发:一个可执行程序里只有一个线程,同时启动多个进程执行,如浏览器
  2. 多线程并发:一个进程内运行多个线程,变量的访问

如:

Value = 100 全局变量

A B A,B两个线程

A,B线程访问Value,访问值都是100

现AB两线程都对Value进行++

操作完成后,Value的值为101,这种情况叫做线程同步问题(后续有讲解)

线程的生存周期

回调函数执行完毕,自然死亡

主线程死亡,被动死亡

并发函数分类实践

如下是一个简单的并发程序描述:

#include

#include 线程库

普通函数

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;

}

此时程序会同时进行上述两个函数

仿函数

class Exec  一个类的仿函数

{

public:

    void operator()()const

    {

       std::cout << "Exec" << std::endl;

    }

};

int main()

{

    Exec e;

    std::thread obj(e);

System(“pause”); 程序暂停至此,不会死亡

return 0;

}

打印Exec

Lambda 

int main()

{

    std::thread obj([] {std::cout << "Lambda" << std::endl; });

System(“pause”); 程序暂停至此,不会死亡

return 0;

}

打印Lambda

综上可知,任何可以调用的类型都可以用于线程对象的构造函数传参

线程死亡

一旦线程启动了,我们就需要知道线程是怎么死的

1.自然死亡 thread析构函数terminate(),主函数执行完毕时,析构函数执行

   非自然死亡 thread析构函数执行完毕时,并发的函数即thread传参函数不一定执行完毕

2.等待 绝对的自然死亡 等待函数执行完毕后,程序再往下走

3.不再等待(主线程存活时后台运行)依赖于主线程的存活

4.如果一个线程是Windows原生线程,主线程销毁后其也会死亡

如下我们验证Windows原生线程的死亡:

包含头文件Windows.h

创建一个原生线程需要调用CreateThread()API

利用CreateThread()API中的一个:

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);API函数,创建了一个原生线程

    return 0;

}

此时运行程序,发现随着主线程的结束该原生线程死亡

等待

以下讲述等待作用:

1.原生线程等待死亡

int main()

{

    HANDLE hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)ThreadCallBack, NULL, NULL, NULL);

 此处是拿个该原生线程的句柄即控制该线程的启动器

    WaitForSingleObject(hThread, -1); -1代表永久等待

    return 0;

}

此时发现运行程序原生线程不会死亡,直到它运行完毕

2.C++并发编程等待死亡:

int main()

{

    std::thread obj(FirstThreadCallBack);

    obj.join(); 阻塞等待,作用是在此处等待函数的返回

    return 0;

}

此时运行程序,不会报错也不会死亡

不再等待

int main()

{

    std::thread obj(FirstThreadCallBack);

    obj.detach(); 不再等待:当其所在命名空间结束时,直接死亡

    for (size_t i = 0; i < 100000; i++)

    {

        std::cout << "main:" << i << std::endl;

    }

如在此处加一个循环,程序在执行该循环时,程序没有死亡,并发函数也不会死亡,而是一起执行两个函数

    return 0;

}

并发特殊情况

情况一

原本在后台运行的线程,由于各种问题,线程提前崩坏,没有正常返回,等待函数没有接收到返回,抛一个异常,遇到此情况跳过即可

情况二

并发线程不仅可以传函数地址,也可以传其他多个参数,如下:

包含头文件: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,"rkvir",50);

    system(“pause”);

    return 0;

}

程序运行,出现一个新现象

在循环执行时,出现

原因:先打印rkvir,然后切片回来,执行system(“pause”),再切片回来打印38

这个现象很形象展示了线程同步问题即丢失操作(后续讲解)

  

你可能感兴趣的:(c++,开发语言)