文章目录
0.线程基本操作(着重std::thread类)
1)创建线程
-创建线程的细节
1.如果传递int这种简单类型的数据,建议都用值传递,不要适用引用类型
2.如果传递类对象作为参数,避免隐式类型转换(比如把一个char*转化为string,把int转化为类A对象),全部都在创建线程这一行就构建出临时对象,然后线程入口函数的形参位置使用引用来作为形参。避免主线程退出导致子线程对内存的非法引用
3.建议不用detach,只使用join,这样就不存在局部变量失效导致线程对内存非法引用的问题
4.线程入口函数要传递类类型形参应该使用引用
void myprint2( A& pmybuf){…}//传递的时候可以用std::ref(myobj)
- 1.c++11的做法
#include <iostream>
#include <thread>
#include <stdio.h>
using namespace std;
void threadproc1()
{
while(true)
{
printf("I am New Thread 1!\n");
}
}
void threadproc2(int a,int b)
{
while(true)
{
printf("I am New Thread 2!\n");
}
}
//在实际开发中并不建议这么做,原因是我们可能需要适用线程对象取控制和管理线程的运行和声明周期
//所以尽量保证线程对象在线程运行期间仍然有效
void func()
{
std::thread t(threadproc1);
t.detach();//一般不会用到detach让线程对象和线程函数脱离关系
//虽然脱离关系后,继续线程对象被销毁,也不影响线程函数的执行
}
int main()
{
//创建线程t1
std::thread t1(threadproc1);
//创建线程t2
std::thread t2(threadproc2,1,2);//1,2是传入的a,b
//
//func();
//权益之计,让主线程不退出
while(true)
{
}
return 0;
}
- 2.lamba表达式创建线程
auto mylanthread = []{
cout<<"我的线程开始执行了"<<endl;
//.........
cout<<"我的线程执行完毕了"<<endl;
};
thread mytobj4(mylanthread);
mytobj4.join();
cout<<"main 主函数执行结束!"<<endl;
2)获取线程ID的方法
- 1)pthread_create的第一个参数(法一和法二获取的ID可能不是全局唯一的,一般是一个很大的数字:内存地址)
- 2)调用pthead_self();
#include <pthread.h>
pthread_t tid = pthread_self();
- 3)通过系统调用获取线程ID(ID是系统范围内全局唯一的,一般是不大的整数,也就是LWP的ID)
#include <sys/syscall.h>
#include <unistd.h>
int tid = syscall(SYS_gettid);
#include <iostream>
#include <thread>
#include <sys/syscall.h>
#include <unistd.h>
#include <stdio.h>
#include <pthread.h>
using namespace std;
void* thread_proc(void* arg)
{
pthread_t* tid1 = (pthread_t*)arg; //调用pthread_create通过第一个参数获取线程ID
int tid2 = syscall(SYS_gettid); //系统调用获取线程ID
pthread_t tid3 = pthread_self(); //pthread_self()获取线程ID
while(true)
{
printf("tid1:%ld, tid2:%ld,tid3:%ld\n",*tid1,tid2,tid3);
sleep(1);
}
}
int main()
{
pthread_t tid;
pthread_create(&tid,NULL,thread_proc,&tid);
pthread_join(tid,NULL);
return 0;
}
- 4)C++ 11获取当前线程的ID的方法
- 类静态方法:std::this_thread类的get_id获取当前线程的ID
- 类实例方法:std::thread的get_id获取指定线程的ID
- get_id方法返回的是一个std::thread::id的包类型,该类型不可以被直接强转成整型,C++11线程库也没有为该对象提供任何转换成整型的接口。
- C++11一般用std::cout这样的输出流来输出,或者先转为std::ostringstream对象,再把字符串转化为我们需要的整型,这算是C++11线程库获取线程ID不太方便的地方:
#include <thread>
#include <iostream>
#include <sstream>
#include <sys/syscall.h>
#include <unistd.h>
#include <stdio.h>
#include <pthread.h>
using namespace std;
void worker_thread_func()
{
while(true)
{
}
}
int main()
{
//1.创建线程
std::thread t(worker_thread_func);
//2.获取线程的ID
std::thread::id worker_thread_id = t.get_id();
std::cout<<"worker thread id:"<<worker_thread_id<<std::endl;//这里打印的格式是id
//3.获取主线程的ID
std::thread::id main_thread_id = std::this_thread::get_id();
//先将std::thread_id转化为std::ostringstream对象
std::ostringstream oss;
oss<<main_thread_id;
//再将std::ostringstream对象转化为std::string
std::string str = oss.str();
std::cout<<"main thread::id:"<<str<<std::endl; //这里打印的是字符串
//最后将std::string转为整数
unsigned long long threadid = std::stoull(str);
std::cout<<"main thread id:"<<threadid<<std::endl; //这里打印的是整数
while(true) //权宜之计,让主线程不退出
{
}
return 0;
}
- 5)总结:
法一法二获取线程ID的结果是一样的,都是pthread_t类型,输出的是一块内存空间地址,可能不是全系统唯一的,一般是很大的数字(内存地址)。而法三获取的线程ID是系统范围内全局唯一的,一般是不大的整数,也就时LWP的ID
3)等待线程结束
(1)linux下等待线程结束
- 函数pthread_join(),其中参数thread是需要等待的线程ID,参数retval是输出参数,用于接收等待退出的线程的退出码(exit Code),可以在调用pthread_exit退出线程时指定线程退出码,也可以在线程函数中通过return语句返回线程退出码
int pthread_join(pthread_t thread, void** retval);//会将当前线程挂起,不消耗CPU时间片
#include <pthread.h>
void pthread_exit(void* value_ptr);//value_ptr可以通过pthread_create第二个参数得到,如果不需要这个参数,可以将其置为NULL
- 代码
#include <thread>
#include <iostream>
#include <sstream>
#include <sys/syscall.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
using namespace std;
#define TIME_FILENAME "time.txt"
void* fileThreadFunc(void* arg)
{
//1.获取当前时间并打印在缓冲区里面
time_t now = time(NULL); //获取当前时间
struct tm*t = localtime(&now);//转化为tm类型
char timeStr[32] = {0};
snprintf(timeStr,32,"%04d/%02d/%02d %02d %02d:%02d:%02d",
t->tm_year+1900, //年
t->tm_mon+1, //月
t->tm_mday, //日
t->tm_hour, //时
t->tm_min, //分
t->tm_sec //秒
);
//2.文件不存在则创建,存在则覆盖
FILE* fp = fopen(TIME_FILENAME,"w");//写方式打开
if(fp == NULL)
{
printf("Failed to create time.txt.\n");
return NULL;
}
//3.写在文件描述符里面
size_t sizeToWrite = strlen(timeStr) + 1;
size_t ret = fwrite(timeStr,1,sizeToWrite,fp);
if(ret != sizeToWrite)
{
printf("Write file error.\n");
}
//4.关闭文件描述符
fclose(fp);
return NULL;
}
int main()
{
//1.创建线程,执行线程执行函数
pthread_t fileThreadID;
int ret = pthread_create(&fileThreadID,NULL,fileThreadFunc,NULL);
if(0 != ret)
{
printf("Failed to create fileThread.\n");
return -1;
}
//2.回收线程
int* retval;
pthread_join(fileThreadID,(void**)&retval);
//3.打开文件描述符,把内容从文件读出来
FILE* fp = fopen(TIME_FILENAME,"r");
if(fp == NULL)
{
printf("open file error.\n");
return -2;
}
char buf[32] = {0};
int sizeRead = fread(buf,1,32,fp);
if(0 == sizeRead)
{
printf("read file error.\n");
fclose(fp);
return -3;
}
printf("Current Time is:%s.\n",buf);
fclose(fp);
return 0;
}
(2)C++ 11提供的等待线程结果的函数
- C++11的std::thread的join方法就是用来等待线程退出的方法,但是需要保证该线程处于运行状态,还有一个joinable可以用来判断这个线程是否可以join
#include <stdio.h>
#include <iostream>
#include <sstream>
#include <sys/syscall.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <time.h>
#include <thread>
using namespace std;
#define TIME_FILENAME "time.txt"
void FileThreadFunc()
{
time_t now = time(NULL);
struct tm*t = localtime(&now);
char timeStr[32] = {0};
sprintf(timeStr,"%04d/%02d/%02d %02d:%02d:%02d",
t->tm_year+1900, //年
t->tm_mon+1, //月
t->tm_mday, //日
t->tm_hour, //时
t->tm_min, //分
t->tm_sec); //秒
//2.文件不存在则创建,存在则覆盖
FILE* fp = fopen(TIME_FILENAME,"w");
if(fp == NULL)
{
printf("Failed to create time.txt.\n");
return;
}
//3.写在文件描述符里面
size_t sizeToWrite = strlen(timeStr) + 1;
size_t ret = fwrite(timeStr,1,sizeToWrite,fp);
if(ret != sizeToWrite)
{
printf("Write file error.\n");
}
//4.关闭文件描述符
fclose(fp);
}
int main()
{
//1.创建线程执行程序并释放线程
std::thread t(FileThreadFunc);
if(t.joinable())
t.join();
//2.打开文件并打印
FILE*fp = fopen(TIME_FILENAME,"r");
if(fp == NULL)
{
printf("open file error.\n");
return -2;
}
char buf[32] = {0};
int sizeRead = fread(buf,1,32,fp);
if(0 == sizeRead)
{
printf("read file error.\n");
fclose(fp);
return -3;
}
printf("Current Time is:%s.\n",buf);
fclose(fp);
return 0;
}
4)惯用法:将C++类对象实例指针作为线程函数的参数
1) C++11的做法(线程函数就不能是类的实例方法)
- 如果适用C++面对对象的方式对线程函数进行封装,线程函数就不能是类的实例方法了,必须是类的静态方法(下面展示C++11的做法)
#include <stdio.h>
#include <iostream>
#include <sstream>
#include <sys/syscall.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <time.h>
#include <thread>
#include <memory>
using namespace std;
class Thread
{
public:
Thread(){}
~Thread(){}
void Start()
{
m_stopped = false;
//threadFunc是类的非静态方法,
//所以作为线程函数,第1个参数必须传递类实例的地址,即this指针
m_spThread.reset(new std::thread(&Thread::threadFunc,this,8888,9999));
}
void stop()
{
m_stopped = true;
if(m_stopped)
{
if(m_spThread->joinable())
{
m_spThread->join();
}
}
}
private:
void threadFunc(int arg1,int arg2)
{
while(!m_stopped)
{
printf("Thread function use instance method.\n");
}
}
private:
std::shared_ptr<std::thread> m_spThread; //智能指针管理线程对象,不需要自己手动删除线程对象了
bool m_stopped; //表示程序是否终止
};
int main()
{
//1.创建线程并初始化
Thread mythread;
mythread.Start();
//2.让主线程不退出
while(true)
{
}
return 0;
}
2)linux中pthread_create通过静态函数的方法
- 若不用C++11的语法,那么线程函数智能适用类的静态方法了,且函数签名必须符合线程函数的签名要求:
- 头文件:Thread.h
//Thread.h
#include <stdio.h>
#include <iostream>
#include <sstream>
#include <sys/syscall.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <time.h>
#include <thread>
#include <memory>
using namespace std;
typedef pthread_t THREAD_HANDLE;
//定义一个线程对象
class CThread
{
public:
CThread();
virtual ~CThread();
//判断创建线程是否创建成功
virtual bool Create();
//获得本线程对象存储的线程句柄
THREAD_HANDLE GetHandle();
//线程睡眠nSeconds秒
void OSSleep(int nSeconds);
void SleepMs(int nMilliseconds);
bool Join();
bool IsCurrentThread();
void ExitThread();
private:
static void* _ThreadEntry(void* pParam);
//虚函数,子类可做一些实例化工作
virtual bool InitInstance();
//虚函数,子类清除实例
virtual void ExitIntance();
//存放线程函数的实际业务逻辑,纯虚函数,子类必须重写该函数
virtual void Run() = 0;
private:
//线程句柄
THREAD_HANDLE m_hThread;
//DWORD m_IDThread; //windows的
};
- Thread.cpp文件的代码
#include "thread.h"
//线程入口函数
void* CThread::_ThreadEntry(void* pParam)
{
CThread* pThread = (CThread*)pParam;
if(pThread->InitInstance()) //若已经创建了实例,那么就可以跑函数的执行函数Run()
{
pThread->Run();
}
pThread->ExitIntance();
return NULL;
}
CThread::CThread()
{
m_hThread = (THREAD_HANDLE)0;
//m_IDThread = 0;
}
CThread::~CThread(){}
bool CThread::Create()
{
if(m_hThread != (THREAD_HANDLE)0)
return true;
bool ret = true;
ret = (0 == ::pthread_create(&m_hThread,NULL,&_ThreadEntry,this));
return ret;
}
//根据实际需要做一些初始化工作
bool CThread::InitInstance()
{
return true;
}
//根据实际需要做一些反初始化工作
void CThread::ExitThread()
{
}
//沉睡秒
void CThread::OSSleep(int seconds)
{
::sleep(seconds);
}
//沉睡微秒
void CThread::SleepMs(int nMilliseconds)
{
::usleep(nMilliseconds);
}
//判断是否是当前线程
bool CThread::IsCurrentThread()
{
return ::pthread_self()== m_hThread;
}
bool CThread::Join()
{
THREAD_HANDLE h_Thread = GetHandle();
if(h_Thread == (THREAD_HANDLE)0) //若还没创立好,返回true
{
return true;
}
//若创建好了,则现在就回收线程
return ( 0 == ::pthread_join(h_Thread,NULL));
}
void CThread::ExitThread()
{
}
3)C++11适用std::bind工具给线程函数绑定this指针
- 这样就能适用类实例方法作为线程函数了
#include <iostream>
#include <thread>
#include <memory>
#include <functional> //必须包含这个头文件,否则报错
using namespace std;
class CIUSocket
{
//省略构造析构函数
public:
void Init()
{
//SendThreadProc和RecvThreadProc都是类的实例化方法
m_spSendThread.reset(new std::thread(std::bind(&CIUSocket::SendThreadProc,this)));
m_spRecvThread.reset(new std::thread(std::bind(&CIUSocket::RecvThreadProc,this)));
}
void Uninit();
void Join();
private:
void SendThreadProc();
void RecvThreadProc();
private:
std::unique_ptr<std::thread> m_spSendThread;
std::unique_ptr<std::thread> m_spRecvThread;
};
5)创建多个线程、数据共享问题分析与案例代码
(1)创建和等待多个线程
#include <iostream>
#include <thread>
#include <memory>
#include <functional> //必须包含这个头文件,否则报错
#include <vector>
using namespace std;
void myprint(int inum)
{
cout<<"myprint 线程开始执行了,线程编号="<<inum<<endl;
//线程干事
cout<<"myprint 线程结束执行了,线程编号="<<inum<<endl;
return;
}
int main()
{
vector<std::thread> mythreads;
//创建五个执行的线程,并放入容器
for(int i = 0;i<5;++i)
{
mythreads.push_back(thread(myprint,i)); //创建并执行线程
}
for(auto iter = mythreads.begin();iter!=mythreads.end();iter++)
{
if(iter->joinable())
iter->join();
}
cout<<"main主函数执行结束"<<endl;
return 0;
}
(2)数据共享问题分析
- 略,根据锁或条件变量来进行操作
(3)共享数据的保护实战范例
#include <iostream>
#include <thread>
#include <memory>
#include <functional> //必须包含这个头文件,否则报错
#include <vector>
#include <list>
#include <mutex>
using namespace std;
class A
{
public:
//把收到的消息放入容器
void inMsgRecvQueue()
{
for(int i = 0;i<10000;i++)
{
cout<<"inMsgRecvQueue()执行,插入一个元素"<<i<<endl;
{
std::lock_guard<std::mutex> sbguard(my_mutex);
msgRecvQueue.push_back(i);
}//自动加锁解锁
}
}
void outMsgRecvQueue()
{
int command = 0;
for(int i = 0;i<10000;i++)
{
bool result = outMsgLULProc(command);
if(result == true)
{
cout<<"outMsgRecvQueue()执行了,从容器中取出一个元素"<<command<<endl;
//处理数据
//。。。
}
else
{
cout<<"outMsgRecvQueue()执行了,但目前收消息队列中是空元素"<<i<<endl;
}
}
cout<<"end"<<endl;
}
bool outMsgLULProc(int& command)
{
std::lock_guard<std::mutex> sbguard(my_mutex);
if(!msgRecvQueue.empty())
{
command = msgRecvQueue.front();
msgRecvQueue.pop_front();
return true;
}
return false;
}
private:
std::list<int> msgRecvQueue;//容器(收消息队列),专门用于代表玩家给咱们发送过来的命令
std::mutex my_mutex;
};
int main()
{
A myobja;
std::thread myOutnMsgObj(&A::inMsgRecvQueue,&myobja);//注意第二个参数必须是引用,用std::ref也行,才能保证线程里面用的是同一个对象
std::thread myInMsgObj(&A::outMsgRecvQueue,&myobja);
myInMsgObj.join();
myOutnMsgObj.join();
return 0;
}
- 加锁后:
1. std::mutex系列
1.mutex C++11 基本的互斥量
2.timed_mutex C++11 有超时机制的互斥量
3.recursive_mutex C++11 可重入的互斥量
4.recursive_timed_mutex C++11 结合timed_mutex和recursize_mutex特点的互斥量
5.shared_timed_mutex C++14 具有超时机制的可共享互斥量
6.shared_mutex C++17 共享的互斥量
std::mutex g_num_mutex;
g_num_mutex.lock();
//干活
g_num_mutex.unlock();
1)std::lock_guard管理锁
自动加锁解锁
std::mutex myMytex;
std::lock_guard<std::mutex> sbguard(myMutex);
2)std::lock管理锁
一次锁住多把锁,但是需要自己unlock解锁
std::mutex myMutex1;
std::mutex myMutex2;
std::lock(myMutex1,myMutex2);
//..
myMutex1.unlock();
myMutex2.unlock();
3)std::lock_guard的std::adopt_lock帮助std::lock解锁
std::mutex myMutex1;
std::mutex myMutex2;
std::lock(myMutex1,myMutex2);
//
std::lock_guard<std::mutex> sbguard1(myMutex1,std::adopt_lock);//帮前面的std::lock解锁
std::lock_guard<std::mutex> sbguard2(myMutex2,std::adopt_lock);
4)unique_lock和它的第二个参数
- unique_lock比lock_guard灵活性更高,但是执行效率差一点,内存占用的也稍微多一些
- unique_lock可以完全取代lock_guard
std:;unique_lock<std::mutex> sbguard1(myMutex);
1.std::adopt_lock
- 不去给互斥量加锁
myMytex.lock();
std::unique_lock<std::mutex> sbguard1(myMutex,std::adopt_lock);
//休眠
std::chorno::milliseconds dura(20000); //卡在这里20s
std::this_thread::sleep_for(dura);
2.std::try_to_lock
- 尝试用mutex的lock去锁定这个mutex,但没锁成功会立即返回,并不会阻塞在那里(前提:程序员不能自己先去lock这个mutex)
//把收到的消息放入容器
void inMsgRecvQueue()
{
for(int i = 0;i<10000;i++)
{
cout<<"inMsgRecvQueue()执行,插入一个元素"<<i<<endl;
std::unique_lock<std::mutex> sbguard1(my_mutex,std::try_to_lock);
if(sbguard1.owns_lock()) //如果条件成立拿到了这个锁
{
msgRecvQueue.push_back(i);
}//自动加锁解锁
else
{
//没拿到锁
cout<<"inMsgRecvQueue()执行,但没拿到锁,只能干点别的事"<<i<<endl;
}
}
return;
}
3.std::defer_lock
- 初始化这个mutex,但是没有给这个mutex加锁
4.unique_lock成员函数
- 1.lock
std::unique_lock<std::mutex>sbguard1(myMutex,std::defer_lock);
sbguard1.lock();
- 2.unlock
- 3.try_lock
void inMsgRecvQueue()
{
for(int i = 0;i<10000;i++)
{
cout<<"inMsgRecvQueue()执行,插入一个元素"<<i<<endl;
std::unique_lock<std::mutex> sbguard1(my_mutex,std::defer_lock);
if(sbguard1.try_lock() == true) //返回true表示拿到了锁,不用管unlock的问题
{
msgRecvQueue.push_back(i);
}//自动加锁解锁
else
{
//没拿到锁
cout<<"inMsgRecvQueue()执行,但没拿到锁,只能干点别的事"<<i<<endl;
}
}
return;
}
- 4.release
返回它所管理的mutex对象指针,并释放所有权,解除两者的关联关系
//把收到的消息放入容器
void inMsgRecvQueue()
{
for(int i = 0;i<10000;i++)
{
cout<<"inMsgRecvQueue()执行,插入一个元素"<<i<<endl;
std::unique_lock<std::mutex> sbguard1(my_mutex);
std::mutex* p_mtx = sbguard1.release();//解除sbguard1和mutex的关系
msgRecvQueue.push_back(i);
p_mtx->unlock();//前面已经加锁,这里需要自己解锁
}
return;
}
5.unique_lock所有权的传递
- 1)法一:移动语义
void inMsgRecvQueue()
{
for(int i = 0;i<10000;i++)
{
cout<<"inMsgRecvQueue()执行,插入一个元素"<<i<<endl;
std::unique_lock<std::mutex> sbguard1(my_mutex);
std::unique_lock<std::mutex> sbguard2(std::move(sbguard1));//移动语义
msgRecvQueue.push_back(i);
}
return;
}
- 2)法二:返回unique_lock类型
void inMsgRecvQueue()
{
for(int i = 0;i<10000;i++)
{
cout<<"inMsgRecvQueue()执行,插入一个元素"<<i<<endl;
std::unique_lock<std::mutex> sbguard1 = rtn_unique_lock();
msgRecvQueue.push_back(i);
}
return;
}
std::unique_lock<std::mutex> rtn_unique_lock()
{
std::unique_lock<std::mutex> tmpguard(my_mutex);
return tmpguard;//从函数返回一个局部unique_lock对象是可以的,返回这种局部对象
//会调用unique_lock的移动构造函数
}
2.std::shared_mutex C++17
- 略
3.std::condition_variable代表条件变量
- 使用时需要绑定一个std::unique_lock或std::lock_guard对象
- 代码:
#include <iostream>
#include <thread>
#include <memory>
#include <functional> //必须包含这个头文件,否则报错
#include <vector>
#include <list>
#include <mutex>
#include <condition_variable>
using namespace std;
class Task
{
public:
Task(int taskID)
{
this->taskID = taskID;
}
void doTask()
{
std::cout<<"handle a task,taskID: "<<taskID<<",threadID: "<<std::this_thread::get_id()<<std::endl;
}
private:
int taskID;
};
std::mutex myMutex;
std::list<Task*> tasks;
std::condition_variable mycv;
void* consumer_thread()
{
Task* pTask = NULL;
while(true)
{
std::unique_lock<std::mutex> guard(myMutex);
while(tasks.empty())
{
//如果发生变化后,如果条件合适,则pthread_cond_wait将直接获得锁
mycv.wait(guard);
}
pTask = tasks.front();
tasks.pop_front();
if(pTask == NULL)
continue;
pTask->doTask();//任务就是打印自己线程号
delete pTask;
pTask = NULL;
}
return NULL;
}
void* producer_thread()
{
int taskID = 0;
Task* pTask = NULL;
while(true)
{
pTask = new Task(taskID);
//使用括号减少guard锁的利用范围
{
std::lock_guard<std::mutex> sbguard(myMutex);
tasks.push_back(pTask);
std::cout<<"produce a task,taskID: "<<taskID<<",threadID :"<<std::this_thread::get_id()<<std::endl;
}
//释放信号量,通知消费者线程
mycv.notify_one();//相当于pthread_cond_signal
++taskID;
//休眠一秒
std::this_thread::sleep_for(std::chrono::seconds(1));
}
return NULL;
}
int main()
{
//创建5个消费者线程
std::thread consumer1(consumer_thread);
std::thread consumer2(consumer_thread);
std::thread consumer3(consumer_thread);
std::thread consumer4(consumer_thread);
std::thread consumer5(consumer_thread);
//创建1个生产者线程
std::thread producer1(producer_thread);
//回收线程
producer1.join();
consumer1.join();
consumer2.join();
consumer3.join();
consumer4.join();
consumer5.join();
return 0;
}