目录
- 一、线程创建
- 二、线程等待
- 三、线程退出
- 四、线程的优缺点
- 五、多线程的创建
- 六、C++11的多线程
- 七、线程分离
一、线程创建
使用接口pthread_create创建新线程,头文件是pthread.h
#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;void* newthreadrun(void* arg)
{while(true){cout << "I am new thread" << endl;sleep(1);}
}
int main()
{pthread_t tid;pthread_create(&tid, nullptr, newthreadrun, nullptr);while(true){cout << "I am main thread" << endl;sleep(1);}return 0;
}
主线程和新线程的pid是一样的,因为属于同一个进程;要查看线程的id,可以获取变量tid打印出来,这个是新线程的id返回给主线程;如果要像getpid一样获取自己的id,可以使用接口pthread_self
string Tohex(pthread_t tid)
{char id[1024];snprintf(id, sizeof(id), "0x%lx", tid);return id;
}
void* newthreadrun(void* arg)
{int cnt = 5;while(cnt){cout << "I am new thread, cnt: "<<cnt <<" pid: "<< getpid()<<" threadid: "<<Tohex(pthread_self())<< endl;sleep(1);cnt--;}return nullptr;
}
int main()
{pthread_t tid;pthread_create(&tid, nullptr, newthreadrun, nullptr);int cnt = 10;while(cnt){cout << "I am main thread, cnt: "<< cnt <<" pid: "<<getpid()<< " tid: "<<Tohex(tid)<<" threadid: "<<Tohex(pthread_self())<<endl;sleep(1);cnt--;}return 0;
}
创建线程的接口的第四个参数可以传参,传入一个字符串作为新线程的名字,注意类型转换。
新线程与主线程谁先运行?不确定,由调度器决定
如果主线程先退出,那么其他的线程都要退出,因为主线程等于进程,进程退出了所有的资源要释放,而所有的线程是共享这些资源的。
把主线程的cnt改为1秒,1秒后新线程也退出
所以一般情况下,往往是主线程最后一个退出,线程也要wait,否则会出现资源泄漏问题。
二、线程等待
使用线程等待的接口:
主线程进行等待,直到其他线程结束才退出
代码:
int n = pthread_join(tid, nullptr);cout << "主线程退出..., n = " << n << endl;sleep(4);
第二个参数可以获取线程的退出信息
三、线程退出
一个进程里面的资源,是所有线程可以共享的。定义一个全局变量,主线程和新线程都可以看到。
int g_val = 100;
void *newthreadrun(void *args)
{string threadname = (char *)args;while (true){printf("g_val: %d, &g_val: %p\n", g_val, &g_val);sleep(1);g_val++;}return (void *)123;
}
int main()
{pthread_t tid;pthread_create(&tid, nullptr, newthreadrun, (void *)"thread-1");while (true){printf("g_val: %d, &g_val: %p\n", g_val, &g_val);sleep(1);}void *ret = nullptr;int n = pthread_join(tid, &ret);cout << "主线程退出..., n = " << n << " ret: " << (long long)ret << endl;sleep(4);return 0;
}
如果任何一个线程出现问题,比如出现了野指针,那么整个进程都会被终止
void *newthreadrun(void *args)
{string threadname = (char *)args;int cnt = 1;while (true){printf("g_val: %d, &g_val: %p\n", g_val, &g_val);sleep(1);if(cnt == 5){int* p = nullptr;*p = 10;}g_val++;cnt++;}return (void *)123;
}
野指针错误是异常信号,也就是说,一旦产生信号,整个进程都会被干掉。
线程退出有三种方式:return、pthread_exit、pthread_cancel
1️⃣return
返回退出码
2️⃣pthread_exit
在新线程中设置pthread_exit
3秒后新线程退出,主线程还在
3️⃣pthread_cancel
指定新线程3秒后退出,主线程继续运行
四、线程的优缺点
优点:
- 创建一个新线程的代价要比创建一个新进程小得多
- 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
- 线程占用的资源要比进程少很多
- 能充分利用多处理器的可并行数量
- 在等待慢速 I/O 操作结束的同时,程序可执行其他的计算任务
- 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
- I/O 密集型应用,为了提高性能,将 I/O 操作重叠。线程可以同时等待不同的I/O 操作
缺点:
- 性能损失。一个很少被外部事件阻塞的计算密集型线程往往无法与其它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。
- 健壮性降低。编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的
- 缺乏访问控制。进程是访问控制的基本粒度,在一个线程中调用某些 OS 函数会对整个进程造成影响
为什么线程切换与进程切换相比需要操作系统做到工作少了很多?
进程运行时,CPU读取进程的上下文信息,用内部的寄存器进行读取。有一个寄存器CR3是指向页表的首地址。读取方式:用地址空间的虚拟地址通过页表映射得到物理内存的数据,然后交给CPU。
线程是进程的其中一个执行流,CPU也要做相同类似的工作,线程切换也要保存上下文数据,只是比进程少一点。进程切换,每次CPU处理某条代码都要到内存中读取,得到数据后,到下一条代码又要做同样的工作,这样效率就低了一些。而线程也要到内存中读取,但是在CPU中,有一个硬件设备叫cache,作用是可以将当前读到的代码的周围其他代码和数据也一并交给CPU,这样下次遇到时,就可以直接在CPU处理了,效率提高了一些,工作也就少了。
那为什么线程可以这样,进程不行呢?因为进程切换时CPU内部的cache的上下文数据也要跟着切换,即清空掉然后换新的一批。线程不一样是因为多个线程在一个进程内部,每个线程是一个进程的其中一个执行流,进程的所有资源(地址空间、页表、内存)都是可以给这些线程共享的,所以线程切换时,cache里面的上下文数据还在。
线程私有:
- 线程的硬件上下文(CPU寄存器的值)
- 线程的独立栈结构
线程共享:
- 代码和全局数据
- 进程文件描述符表
线程安全问题:一个线程出问题,导致其他线程也出问题,整个进程就会退出
公共函数被重入:多线程中,函数被多个线程同时进入
五、多线程的创建
用for循环5次,创建5个线程,然后打印它们的名字
const int threadnum = 5;
void* taskthread(void* args)
{char* threadname = static_cast<char*>(args);while(true){cout << threadname << endl;sleep(2);}delete []threadname;return nullptr;
}
int main()
{vector<pthread_t> threads;for(int i=0;i<threadnum;i++){char threadname[64];snprintf(threadname, sizeof(threadname), "thread-%d", i+1);pthread_t tid;pthread_create(&tid, nullptr, taskthread, threadname);threads.push_back(tid);}for(auto e:threads){pthread_join(e, nullptr);}return 0;
}
发现后面都变成5了,因为这些线程用的是同一个缓冲区,所以后面的线程名字把前面的覆盖了。
修改:每个线程有自己的空间
pthread_create的第四个参数不仅可以传递整型变量,还可以传对象,自定义的类型
让线程完成加法:
class Task
{
public:Task(){}void SetData(int x, int y){_x = x;_y = y;}int Excute(){return _x + _y;}~Task(){}
private:int _x;int _y;
};
class DataThread
{
public:DataThread(int x, int y, const string& threadname):_threadname(threadname){_t.SetData(x, y);}int run(){return _t.Excute();}string getName(){return _threadname;}~DataThread(){}
private:Task _t;string _threadname;
};
const int threadnum = 5;
void* taskthread(void* args)
{DataThread* td = static_cast<DataThread*>(args);string threadname = td->getName();int ret = td->run();cout << threadname<<" ret is: " <<ret<<endl;delete td;return nullptr;
}
int main()
{vector<pthread_t> threads;for(int i=0;i<threadnum;i++){char threadname[64];snprintf(threadname, sizeof(threadname), "thread-%d", i+1);DataThread* td = new DataThread(10, 20, threadname);pthread_t tid;pthread_create(&tid, nullptr, taskthread, td);threads.push_back(tid);}for(auto e:threads){pthread_join(e, nullptr);}return 0;
}
六、C++11的多线程
C++11的多线程是对原生线程的封装,为了语言的跨平台性
void newthread(int num)
{while (true){cout << "I am new thread-" << num << endl;sleep(1);}
}
int main()
{thread t1(newthread, 1);thread t2(newthread, 2);thread t3(newthread, 3);while (true){cout << "I am main thread-" << endl;sleep(1);}t1.join();t2.join();t3.join();return 0;
}
对原生线程做封装:
#ifndef __THREAD_HPP__
#define __THREAD_HPP__
#include <iostream>
#include <string>
#include <vector>
#include <pthread.h>
#include <unistd.h>
#include <functional>using namespace std;namespace yss
{template <class T>using func_t = function<void(T &)>;template <class T>class Thread{public:void Excute(){_func(_data);}Thread(func_t<T> func, const T &data, const string &threadname): _func(func), _data(data), _threadname(threadname), _stop(true){}// 注意staticstatic void *runthread(void *args) // 类成员函数,形参有this指针{Thread<T> *self = static_cast<Thread<T> *>(args);self->Excute();return nullptr;}// 创建bool Satrt(){int n = pthread_create(&_tid, nullptr, runthread, this);if (!n) // 创建成功{_stop = false;return true;}else{return false;}}// 分离void Detach(){if (!_stop){pthread_detach(_tid);}}// 等待void Join(){if (!_stop){pthread_join(_tid, nullptr);}}void Stop(){_stop = true;}string name(){return _threadname;}~Thread(){}private:pthread_t _tid;string _threadname;bool _stop;func_t<T> _func;T _data;};
}
#endif
七、线程分离
默认情况下,新创建的线程是 joinable 的,线程退出后,需要对其进行pthread_join 操作,否则无法释放资源,从而造成系统泄漏
如下是主线程等待新线程
void* newthread(void* args)
{int cnt = 5;while(cnt){cout << "I am new thread" << endl;sleep(1);cnt--;}return nullptr;
}
int main()
{pthread_t tid;pthread_create(&tid, nullptr, newthread, nullptr);cout << "thread wait block" << endl;pthread_join(tid, nullptr);cout << "thread wait return" << endl;return 0;
}
如果不要等待,必须对新线程进行分离(可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离)
接口:
分离的线程不需要等待,主线程可以做自己的事情
void* newthread(void* args)
{int cnt = 5;while(cnt){cout << "I am new thread" << endl;sleep(1);cnt--;}return nullptr;
}
int main()
{pthread_t tid;pthread_create(&tid, nullptr, newthread, nullptr);//分离新线程pthread_detach(tid);while(true){cout << "I am main thread" << endl;sleep(1);}// cout << "thread wait block" << endl;// pthread_join(tid, nullptr);// cout << "thread wait return" << endl;return 0;
}
如果让主线程5秒结束,分离的线程死循环:
void* newthread(void* args)
{while(true){cout << "I am new thread" << endl;sleep(1);}return nullptr;
}
int main()
{pthread_t tid;pthread_create(&tid, nullptr, newthread, nullptr);//分离新线程pthread_detach(tid);int cnt = 5;while(cnt--){cout << "I am main thread" << endl;sleep(1);}return 0;
}
线程分离了,但是资源依然是共享的,主线程先退出,那么整个进程就会结束,分离的线程也同样退出。
如果分离的线程中的某个函数有异常(比如除0错误),整个进程会直接退出,并打印报错信息
void* newthread(void* args)
{while(true){cout << "I am new thread" << endl;sleep(1);int a = 10;a /= 0;}return nullptr;
}
int main()
{pthread_t tid;pthread_create(&tid, nullptr, newthread, nullptr);//分离新线程pthread_detach(tid);while(true){cout << "I am new thread" << endl;sleep(1);}return 0;
}