目录
线程控制
pthread库
与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的
要使用这些函数库,要通过引入头文<pthread.h>
链接这些线程函数库时要使用编译器命令的“-lpthread”选项
创建 :
main和new线程谁先运行?不确定
等待:
操作:
问题:
我们期望谁最后退出?main thread 进行等待擦屁股。join来保证main线程最后退出,join方法会阻塞主线程,直到调用的线程完成执行
如果不join的话,如果主线程退了main函数退了代表函数结束了则进程结束了,所有线程都会退;不join会造成类似僵尸进程的问题 ;
tid是什么样子的?
是一个库当中的线程数据块的起始地址;线程属性集合的起始虚拟地址--在pthread库中维护全面看待线程函数传参:我们可以传递任意类型,但你一定要能想得起来,也能传递类(class)对象的地址
但是我们不太推荐直接在main函数,因为我们的整形,字符串,class是自己定义的在main函数的栈上开辟的空间,所以我们刚刚传递进去的td,是新线程访问了主线程栈上的变量;我们不太推荐因为一方面它破坏了主线程的完整性和独立性,其次如果在来一个线程也访问主函数栈上的定义的临时变量,那么他们两个线程对数据是读取还好但是修改呢?会影响另一个线程;我们更推荐在堆上申请一段空间然后把堆申请的临时变量例如指针拷贝给线程,那么就相当于你在你的主线程里面创建了堆空间因为这个堆空间一旦申请出来了其他线程也能看到但必须得有地址,只要把地址传递给拷贝给线程,那么堆空间就相当于交给这个线程了,未来在想要第二个线程,你再重新new堆上的空间再交给新线程,这样大家人手一个堆空间,多线程就不会干扰了;
等待函数传参 :
#include <iostream> #include <unistd.h> #include<ctime> #include<string> #include<pthread.h> class thread{ public: std::string name; int a; }; void*threadrun(void*agv){ //std::string name=(char*)agv; //int a=*(int*)agv;//传进来a的地址再强转+解引用 thread *td=static_cast<thread*>(agv);//安全类型的强转,(thread*)agv int cnt=5; while(cnt){ std::cout<<td->name<<" run thread ,cnt: "<<cnt--<<std::endl; sleep(1); } delete td; return (void*)111; } std::string printfto(pthread_t &tid){ char buffer[64]; snprintf(buffer,sizeof(buffer),"0x%lx",tid);//%lx用于输出无符号长整数 return buffer; } int main(){ pthread_t tid;//每个线程都要有自己的线程id;是由pthread库提供的typedef unsigned long int pthread_t;无符号整数 //main和new线程谁先运行?不确定 //int a=100; thread *td=new thread(); td->name="thread 1"; td->a=100; int n=pthread_create(&tid,nullptr,threadrun,(void*)td);//创建,注意最后一个参数加void*,并且第四个传递的是地址 if(n!=0){//失败 std::cerr<<"create thread error"<<std::endl; return 1; } //见见tid std::string str=printfto(tid);//16进制打印 std::cout<<"tid: "<<str<<std::endl; std::cout<<"main thread join begin......"<<std::endl; //我们期望谁最后退出?main thread ,你如何保证呢? void*code=nullptr;//注意这里是一个指针,并且开辟了空间的 n=pthread_join(tid,&code);//main进行等待, join来保证main线程最后退出,join方法会阻塞主线程,直到调用的线程完成执行//如果不join的话,如果主线程退了main函数退了代表函数结束了则进程结束了,所有线程都会退;不join会造成类似僵尸进程的问题 if(n==0){ std::cout<<"main thread wait succes!new thread exit codeL "<<(uint64_t)code<<std::endl; } return 0; }
全面看待线程函数返回:
a.只考虑正确的返回,不考虑异常,因为异常了则整个进程都崩溃了包括主线程(任何一个线程崩掉了意味着这个进程崩了,那么意味着这个进程的父进程关心他的退出情况)b.我们可以传任意类型包括类对象
如何创建多线程?
错误创建:正确创建:
等待:
线程的终止:
新线程如何终止?
1. 函数return。只要新线程把自己的函数执行完了就代表新线程执行结束,我们可以在函数里调用各种函数和程序替换,但是只要return了就结束了
exit()退出:不能用来线程,他是专门用来进程退出的
2. pthread_exit:专门用来终止一个线程的
3. pthread_cancel:用于请求取消指定的线程,新线程的退出结果是-1
在 POSIX 线程(pthread)库中,PTHREAD_CANCELED 是一个特殊的常量,用于指示线程已被取消。
可不可以不join线程,让他执行完毕就退出呢?可以
主线程如何终止?
main函数结束,主线程结束表示整个进程都结束了,所以尽量保证主线程最后退出保证其他新线程执行完毕;
问题:
可不可以不join线程,让他执行完毕就退出呢?可以
a. 一个线程被创建,默认是joinable的,必须要被join的
b. 如果一个线程被分离,线程的工作状态分离状态,不需要/不能被join的!依旧属于进程内部但是不需要被join;
那么此时主线程就自己做自己的事情;即使这个线程被分离了只要这个线程出问题那么进程还是被退出;
#include <iostream>
#include <unistd.h>
#include <ctime>
#include <vector>
#include <string>
#include <pthread.h>
const int num = 10;
void *threadrun(void *agv)
{
pthread_detach(pthread_self());//自己把自己给分离,之后主线程就不用等了
std::string name = static_cast<const char *>(agv); // 强转
while (true)
{
std::cout << name << std::endl;
sleep(3);
break;
}
//exit(1);
//return agv;
pthread_exit(agv);//专门用来终止一个线程的,它的返回值跟return的效果一样
}
std::string printfto(pthread_t &tid){
char buffer[64];
snprintf(buffer,sizeof(buffer),"0x%lx",tid);//%lx用于输出无符号长整数
return buffer;
}
int main()
{
std::vector<pthread_t> tids; // 用vector放线程id
for (int i = 0; i < num; i++)
{ // 用循环创建多线程
// 1.线程的id
pthread_t id;
// 2.线程名字
char *name = new char[128];// 用new创建,这样的话每次就是将堆空间的地址传递给线程了,所以每个线程就不会互相干扰了
snprintf(name, 128, "thread-%d", i + 1);// 此时不能用sizeof(name)了因为这样的话就是8了
pthread_create(&id, nullptr, threadrun, /*线程的名字*/ name); // 创建
//3.保存所有线程的id信息
tids.emplace_back(id);
}
//sleep(5);
for(auto tid:tids){
pthread_detach(tid);//主线程分离其他线程,前提是线程得存在
}
//等待
for(auto tid:tids){
// pthread_cancel(tid);//取消
// std::cout<<"cancel: "<<tid<<std::endl;
//堆空间在 for 循环结束后仍然存在,直到你显式地释放它。
void*result=nullptr;//线程被取消线程的返回结果是-1,#define PTHREAD_CANCELED ((void *) -1)
//PTHREAD_CANCELED
int n=pthread_join(tid,&result);
std::cout<<(long int)result<<" quit...: "<<n<<std::endl;//他的返回值是整数
//delete (char*)name;
}
//sleep(100);
return 0;
}
pthread_detach:是 POSIX 线程(pthread)库中的一个函数,用于将线程的状态设置为“分离”(detached)。一旦线程被分离,它的资源会在线程结束时自动释放,这样就不需要其他线程调用 pthread_join 来回收它的资源。
C++11多线程
我们上述学的是原生线程库的方法,但是c++已经支持多线程了;
c++11的多线程本质就是对原生线程库接口的封装!!
所以在linux当中你的c++要支持多线程,其实底层必须封装linux环境的pthread库,编译都得带-lpthread
在Linux平台下比如java,python,c++以及其他语言自己自带多线程,这些多线程未来一定要和linux原生线程库产生关联
封装线程
简单测试
main.cc
#include <iostream>
#include <unistd.h>
#include "thread.hpp"
using namespace std;
void print(const string &name){
int cnt=1;
while(true){
cout<<name<<"is running ,cnt: "<<cnt<<endl;
sleep(1);
}
}
using namespace threadmodel; // 使用自定义的命名空间
int main()
{
thread t("thread-1", print);
t.start();
cout << "name: " << t.threadname() << " ,status: " << t.status() << endl;
sleep(10);
cout << "name: " << t.threadname() << " ,status: " << t.status() << endl;
t.stop();
sleep(1);
cout << "name: " << t.threadname() << " ,status: " << t.status() << endl;
t.join();
cout<<" join done"<<endl;
return 0;
}
thread.hpp
#pragma once
#include <iostream>
#include <string>
#include <pthread.h>
#include<functional>//回调方法
using namespace std;
namespace threadmodel//构造一个命名空间
{
//线程的执行方法,后面可以调整
typedef void(*func_t)(const string &name);//func_t 是一个指向返回类型为 void 且有参数的函数的指针。
//func_t 现在可以用作一个函数指针类型的别名,表示任何指向带字符串参数且返回类型为 void 的函数的指针。
class thread
{
public:
void excute(){
_running=true;
_func(_name);
}
public:
thread(const string &name,func_t func):_name(name),_func(func)
{
}
static void* threadroutine(void* agv){//因为是类内定义的方法,所以会隐含一个this指针参数,加了static就可以,这是因为 static 成员函数不与类的实例相关联,因此它不需要 this 指针。
//只要线程启动,新线程都会启动这个方法
thread* self=static_cast<thread*>(agv);//获得当前对象。因为要调用_func,但_func是动态的,静态函数无法访问所以传this指针访问
self->excute();
return nullptr;
}
bool start(){//线程启动方法
int n=::pthread_create(&_tid,nullptr,threadroutine,this);
//使用::pthread_create确保调用的是全局命名空间中的pthread_create函数,避免当前命名空间内可能存在的同名函数的影响。
//直接使用 pthread_create 会根据当前命名空间查找,如果找到了同名函数,就会调用那个函数。
if(n!=0)return false;
return true;
}
string status(){
if(_running){
return "running";
}
else return "sleep";
}
void stop(){//线程停止方法
if(_running){//得先有线程才能停止
_running=false;//状态停止
::pthread_cancel(_tid);
}
}
void join(){//线程等待方法
if(!_running){//没有running才值得join
::pthread_join(_tid,nullptr);
}
}
string threadname(){
return _name;
}
~thread(){
}
private:
string _name; // 线程的名字
pthread_t _tid; // 线程的id
bool _running; // 是否处于工作状态
func_t _func;//线程要执行的回调函数
};
};
问题:
多线程封装
main.cc
#include <iostream>
#include <unistd.h>
#include <vector>
#include "thread.hpp"
using namespace std;
void print(const string &name)
{
int cnt = 1;
while (true)
{
cout << name << "is running ,cnt: " << cnt << endl;
sleep(1);
}
}
using namespace threadmodel; // 使用自定义的命名空间
const int gun = 10;
int main()
{
// 构建线程对象
vector<thread> threads;
for (int i = 0; i < gun; i++)
{
string name = "thread-" + to_string(i + 1);
threads.emplace_back(name, print);
sleep(1);
}
// 统一启动
for (auto &threadd : threads)
{
threadd.start();
}
sleep(10);
// 统一结束
for (auto &threadd : threads)
{
threadd.stop();
}
// 等待
for (auto &threadd : threads)
{
threadd.join(); // 等待
}
return 0;
}
thread.hpp
#pragma once
#include <iostream>
#include <string>
#include <pthread.h>
#include <functional> //回调方法
using namespace std;
namespace threadmodel // 构造一个命名空间
{
// 线程的执行方法,后面可以调整
typedef void (*func_t)(const string &name); // func_t 是一个指向返回类型为 void 且有参数的函数的指针。
// func_t 现在可以用作一个函数指针类型的别名,表示任何指向带字符串参数且返回类型为 void 的函数的指针。
class thread
{
public:
void excute()
{
cout << _name << " is running ! " << endl;
_running = true;
_func(_name); // 回调不仅回调运行,他还得结束
_running = false; // 回调完就结束了
}
public:
thread(const string &name, func_t func) : _name(name), _func(func)
{
cout << "create: " << name << " done! " << endl;
}
static void *threadroutine(void *agv)//只要线程启动,新线程都会启动这个方法
{ // 因为是类内定义的方法,所以会隐含一个this指针参数,加了static就可以,这是因为 static 成员函数不与类的实例相关联,因此它不需要 this 指针。
thread *self = static_cast<thread *>(agv); // 获得当前对象。因为要调用_func,但_func是动态的,静态函数无法访问所以传this指针访问
self->excute();
return nullptr;
}
bool start()
{ // 线程启动方法
int n = ::pthread_create(&_tid, nullptr, threadroutine, this);
// 使用::pthread_create确保调用的是全局命名空间中的pthread_create函数,避免当前命名空间内可能存在的同名函数的影响。
// 直接使用 pthread_create 会根据当前命名空间查找,如果找到了同名函数,就会调用那个函数。
if (n != 0)
return false;
return true;
}
string status()
{
if (_running)
{
return "running";
}
else
return "sleep";
}
void stop()
{ // 线程停止方法
if (_running)
{ // 得先有线程才能停止
_running = false; // 状态停止
::pthread_cancel(_tid);
cout << _name << " stop ! " << endl;
}
}
void join()
{ // 线程等待方法
if (!_running)
{ // 没有running才值得join
::pthread_join(_tid, nullptr);
cout << _name << " join ! " << endl;
}
}
string threadname()
{
return _name;
}
~thread()
{
}
private:
string _name; // 线程的名字
pthread_t _tid; // 线程的id
bool _running; // 是否处于工作状态
func_t _func; // 线程要执行的回调函数
};
};
总之是在管理原生线程 !!先描述(class thread)后组织(vector<thread>)
错误检查:
1. 传统的一些函数是,成功返回0,失败返回-1,并且对全局变量errno赋值以指示错误。
2. pthreads函数出错时不会设置全局变量errno(而大部分其他POSIX函数会这样做)。而是将错误代码通过返回值返回
3. pthreads同样也提供了线程内的errno变量,以支持其它使用errno的代码。对于pthreads函数的错误, 建议通过返回值业判定,因为读取返回值要比读取线程内的errno变量的开销更小
野指针:
野指针是指向一个未定义或不再有效内存区域(已经释放的即delete或不再有效的内存)的指针 ;
未初始化的指针:指针在声明后未被赋值,默认为随机值。
int* p; // 未初始化的指针
释放后将指针置为 nullptr:在释放内存后,立即将指针设置为 nullptr,防止继续访问已释放的内存。
delete p; // 释放内存 p = nullptr; // 防止成为悬空指针
int *p = nullptr; 将指针 p 初始化为 nullptr,这意味着此时 p 不指向任何有效的内存地址。此时再想用只能p=new int,*p=100;不能直接*p=100;
int *p = nullptr;// 初始化指针为 nullptr // 动态分配内存并赋值 p = new int; // 分配内存 *p = 42; // 给指针指向的内存赋值 // 释放内存 delete p; // 释放动态分配的内存 p = nullptr; // 防止成为野指针