C++学习记录:多线程相关
之前学过一点C语言多线程方面的内容(pthread.h
),但是仅仅是会用,对多线程的实现原理什么的基本上算是不了解。接下来,我的网络编程学习要进一步对代码进行优化,其中肯定少不了对多线程的运用,所以在进行下一步之前,先系统的学习一下多线程。
本篇学习记录使用的语言为C++,调用的线程库为C++11里的std::thread
库。
零、基本概念
一个进程可以有多个线程,而一个线程只能属于一个进程。
单核CPU的多线程执行为每条线程代码执行一段时间后进行切换,实际还是为同时进行一条线程,因为切换的速度很快,给人一种同时进行的错觉;而多核CPU的多线程执行为同时进行多条线程,当线程数量大于核数量时,也会进行线程的切换,保证线程的进行。
多线程的执行是抢占式的,即多条线程下,没有运行顺序的规律,
一、基本线程创建 thread
如下所示,新建一个thread对象,构造函数传参第一个为线程执行函数,随后为执行函数的传参。
#include<thread>
using namespace std;
thread *t1 = new thread(work,1111);
void work(int a)
{
for(int n=0;n<a;n++)
{
printf("%d\n",a);
}
}
如下所示,新建一个thread数组,构造函数传参第一个为线程执行函数,随后为执行函数的传参。
#include<thread>
using namespace std;
thread *t[4];
for(int n=0;n<4;n++)
{
t[n] = new thread(work,n);
}
void work(int a)
{
for(int n=0;n<a;n++)
{
printf("%d\n",a);
}
}
二、等待/分离 join/detach
join是在main函数中等待线程执行完才继续执行main函数,detach则是把该线程分离出来,不管这个线程执行得怎样,往下继续执行main函数。
- join操作会等待线程执行完毕,然后回收该线程资源;detach操作则不会等待线程完成,线程资源的回收由init进程完成。
#include<thread>
using namespace std;
thread *t[4];
for(int n=0;n<4;n++)
{
t[n] = new thread(work,n);
t[n]->detach();//线程分离
t[n]->join();//线程不分离
}
三、锁 lock/unlock
由于线程的执行是抢占式的,且变量资源等是共享的,对于多条线程同时执行的情况下,可能对同一段内容同时执行,其中涉及到的变量操作等就会产生错误。类似同时对一个变量进行运算操作,由于赋值前的值不同,结果也就不同,导致数据出现问题。此时就可以使用锁的操作防止此类错误发生。
- 上锁的区域同时只能被一条线程执行,由此来解决同时执行造成错误的问题。
#include<mutex>//锁的头文件
#include<thread>
using namespace std;
mutex m;//锁的变量
void work(int a)
{
for(int n=0;n<4;n++)
{
m.lock();//临界区域 开始 锁掉相关区域 避免同时调用printf操作导致打印混乱
printf("%d\n",a);
m.unlock();//临界区域 结束 解锁
}
}
但是不停的上锁和解锁很容易忘记解锁,就会出现该段代码无法被执行,导致程序出现问题,且不会报错。为了避免这种情况,我们可以使用自解锁。自解锁会自动对所处区间的代码进行上锁和解锁操作,从而防止忘解锁的情况发生。
- 自解锁的大致原理其实就是构造器进行上锁,析构器进行解锁…
#include<mutex>//锁的头文件
#include<thread>
using namespace std;
mutex m;//锁的变量
void work(int a)
{
for(int n=0;n<4;n++)
{
lock_guard<mutex>lock1(m); //自解锁
printf("%d\n",a);
}
}
四、原子操作 atomic
频繁的上锁解锁操作会非常耗时,如果上锁区域执行的代码很少的话会非常不划算。如果我们在多线程中需要对变量操作的话,频繁的给变量操作区域上下锁性价比很低,此时我们可以使用原子变量。
- 原子变量同时只能被一条线程操作,相比读写锁,速度快了不止一个量级。
#include<iostream>
#include<thread>
#include<atomic>//原子操作
using namespace std;
atomic_int count(0); //原子变量
void work(int a)
{
for(int n=0;n<2;n++)
{
printf("other thread:%d\n",a);
count++;
}
}
int main()
{
thread *t[4];
for(int n=0;n<4;n++)
{
t[n] = new thread(work,n);
t[n]->join();
}
for(int n=0;n<2;n++)
{
printf("main thread\n");
count++;
}
while(1)
{
cout<<count;
getchar();
}
return 0;
}