多任务
多任务分为:基于进程和基于线程
基于进程:是程序的并发执行
基于线程:是同一程序的不同片段的并发执行
创建线程
包含头文件#include<pthread.h>
个人觉得这个不好用,vs上也不能用,但是不需要c++11
pthread_create(pthread_id,arr,function,arg);
参数分别为:
pthread_id | arr | function | arg |
---|---|---|---|
线程id | 属性 | 函数 | 函数参数 |
c++11头文件<thread.h>
推荐使用这个
参考:https://www.cnblogs.com/whlook/p/6573659.html
thread th1(function,参数);
join()是等待线程结束,进程才结束
detach()是线程独立运行(这个控制不好就不要用)
示例:
#include<thread>
#include<iostream>
using namespace std;
void fun(int i)
{
for(int i=0;i<20;i++)
cout<<i<<endl;
}
int main()
{
thread th(fun,2);
th.join(); //不加的话会报错,因为线程还在继续,但是进程已经结束,资源已经被释放
}
linux编译:
g++ test.cpp -std=c++11 -o thread.o -lpthread
线程安全
线程互斥
简单来说,就是一个资源在某个时间段只能被一个线程占用
线程同步
多个线程按照先后顺序执行,大部分的线程同步都已经完成了线程的互斥
互斥锁mutex
头文件
示例:(利用全局变量作通讯)
没有互斥锁的代码及结果:
#include<iostream>
#include<thread>
int cnt=20;
void fun1()
{
while(cnt>0)
{
cnt--;
cout<<cnt<<endl;
}
}
void fun2()
{
while(cnt>0)
{
cnt--;
cout<<cnt<<endl;
}
}
int main()
{
thread th1(fun1);
thread th1(fun2);
th1.join();
th2.join();
}
输出结果不确定:
比如:
19 18 17 16 … 2 1 0 6(6出现在奇怪的位置)
解决方法加上互斥锁mutex
#include<mutex>
#include<Windows.h>
#include<iostream>
#include<thread>
int cnt=20;
mutex m;
void fun1()
{
while(cnt>0)
{
m.lock();
cnt--;
cout<<cnt<<endl;
m.unlock();
Sleep(100); //留时间给fun2去抢资源,不然fun1会一直把资源锁住
}
}
void fun2()
{
while(cnt>0)
{
m.lock();
cnt--;
cout<<cnt<<endl;
m.unlock();
Sleep(100);
}
}
int main()
{
thread th1(fun1);
thread th1(fun2);
th1.join();
th2.join();
}
输出结果:
19 18 … 1 0 -1
但是mutex也有个问题,如过某个线程出现异常退出,mutex没有解锁,那样资源永远被锁住
使用lock_guard则相对安全,它是基于作用域的,能够自解锁,当该对象创建时,它会像m.lock()一样获得互斥锁,当生命周期结束时,它会自动析构(unlock),不会因为某个线程异常退出而影响其他线程。示例:
int cnt = 20;
mutex m;
void t1()
{
while (cnt > 0)
{
lock_guard<mutex> lockGuard(m);
if (cnt > 0)
{
--cnt;
cout << cnt << endl;
}
}
}
void t2()
{
while (cnt > 0)
{
lock_guard<mutex> lockGuard(m);
if (cnt > 0)
{
--cnt;
cout << cnt << endl;
}
}
}
abort()和exit()和return
1.return是函数结束,析构局部变量,返回一个值,如果这个函数是main那么,系统会调用exit析构全局变量
2.exit(0)或者exit(-1),直接析构全局变量,正常结束进程,返回一个值
3.abort(),直接结束进程。
exit和abort有可能会导致内存泄漏
线程和进程的区别
1.线程共享内存,栈是独享的,所以适合高并发
2.每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
3.从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。
4.线程是进程的组成部分
进程和程序的区别
进程概念和程序概念最大的不同之处在于:
(1)进程是动态的,而程序是静态的。
(2)进程有一定的生命期,而程序是指令的集合,本身无“运动”的含义。没有建立进程的程序不能作为1个独立单位得到操作系统的认可。
(3)1个程序可以对应多个进程,但1个进程只能对应1个程序。进程和程序的关系犹如演出和剧本的关系。
什么是临界区
参考:https://baike.baidu.com/item/临界区/8942134?fr=aladdin
临界区也像是一种互斥锁
进程进入临界区的调度原则是:
1、如果有若干进程要求进入空闲的临界区,一次仅允许一个进程进入。
2、任何时候,处于临界区内的进程不可多于一个。如已有进程进入自己的临界区,则其它所有试图进入临界区的进程必须等待。
3、进入临界区的进程要在有限时间内退出,以便其它进程能及时进入自己的临界区。
4、如果进程不能进入自己的临界区,则应让出CPU,避免进程出现“忙等”现象。
下面通过一段代码展示了临界区在保护多线程访问的共享资源中的作用。通过两个线程来分别对全局变量g_cArray[10]进行写入操作,用临界区结构对象g_cs来保持线程的同步,并在开启线程前对其进行初始化。为了使实验效果更加明显,体现出临界区的作用,在线程函数对共享资源g_cArray[10]的写入时,以Sleep()函数延迟1毫秒,使其他线程同其抢占CPU的可能性增大。
// 临界区结构对象
CRITICAL_SECTION g_cs;
// 共享资源
char g_cArray[10];
UINT ThreadProc10(LPVOID pParam)
{
// 进入临界区
EnterCriticalSection(&g_cs);
// 对共享资源进行写入操作
for (int i = 0; i < 10; i++)
{
g_cArray[i] = a;
Sleep(1);
}
// 离开临界区
LeaveCriticalSection(&g_cs);
return 0;
}
UINT ThreadProc11(LPVOID pParam)
{
// 进入临界区
EnterCriticalSection(&g_cs);
// 对共享资源进行写入操作
for (int i = 0; i < 10; i++)
{
g_cArray[10 - i - 1] = b;
Sleep(1);
}
// 离开临界区
LeaveCriticalSection(&g_cs);
return 0;
}
……
void CSample08View::OnCriticalSection()
{
// 初始化临界区
InitializeCriticalSection(&g_cs);
// 启动线程
AfxBeginThread(ThreadProc10, NULL);
AfxBeginThread(ThreadProc11, NULL);
// 等待计算完毕
Sleep(300);
// 报告计算结果
CString sResult = CString(g_cArray);
AfxMessageBox(sResult);
}