目录
1.铺垫
在LINUX内核中是不存在线程的概念的,只有轻量级进程的概念,但是在用户的角度来看,用户就是要把轻量级进程当做线程的使用,所以将轻量级进程的控制接口,封装成了一个线程库,用户直接使用库,也就屏蔽了底层的实现。
所以在g++编译的时候记得 加上 -lpthread选项,让可执行程序链接ptread库
2.验证
创建一个新的线程
参数thread,是输出型参数,创建进程的的id。
参数attr是一个包含创建时间,属性的结构体,用于初始化新创建的线程,设置为NULL采用默认初始化。
start_routine是一个函数指针,线程会执行函数的内容。
arg是start_routine的参数。
cpp
#include <iostream>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
using namespace std;
void *start_routine(void *v)
{
while (true)
{
cout << "I am new thread pid: " << getpid() << endl;
sleep(1);
}
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, &start_routine, nullptr);
while (true)
{
cout << "I am main thread pid:" << getpid() << endl;
sleep(1);
}
return 0;
}
我们发现这两线程的pid是同一个,这是因为他们属于同一个进程。
那操作系统调度的时候怎么区分他们两个呢?
别忘了,进程和线程在liunx下都叫轻量级进程LWP
让我们使用 ps -aL 选项查看进程的所有轻量级进程。
我们发现有两个LWP449636,449637,其中449636就是main函数的执行流,也就是说当进程内部之有一个执行流的时候PID==LWP。
3.线程的等待
关联一个结束的进程
参数thread是线程的id,这个id来源自pthread库,而不是系统内核的LWP,他们的关系是一 一对应的。
参数retval是一个输出型参数,用于保存目标线程的退出值。
4.线程退出
线程退出是不可以使用exit的,因为exit是给进程用的,使用exit直接整个进程就干掉了
1.return可以退出线程。
终止调用的线程。
3.
终止参数thread线程
5.demo代码
v1
cpp
#include <iostream>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
#include <vector>
#include <string>
#include <cstdio>
#include <cstdlib>
using namespace std;
// 创建5个进程,分别让他们执行任务,获取返回的结果
enum
{
sum = 5
};
void *handler(void *arg)
{
char* threadname = static_cast<char*>(arg);
cout << "I am: " << threadname << endl;
delete[] threadname;
return (void *)1;
}
int main()
{
// 创建线程
vector<pthread_t> tids;
for (int i = 0; i < sum; i++)
{
sleep(1);
char *buffer = new char[64];
pthread_t tid;
snprintf(buffer, 64, "thread %d", i + 1);
pthread_create(&tid, nullptr, handler, buffer);
tids.push_back(tid);
}
for (auto &t : tids)
{
void *ret;
int n = pthread_join(t, &ret);
cout << "main thread get ret:" << (long long)ret << " " << "n:" << n << endl;
}
return 0;
}
v2
别忘了pthread_create最后一个参数类型是void* 这意味着可以和c++的类结合起来,不仅仅是传字符串,传整形,同样返回也可以使用类。
cpp
#include <iostream>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
#include <vector>
#include <string>
#include <cstdio>
using namespace std;
// 创建5个进程,分别让他们执行任务,获取返回的结果
enum
{
sum = 5
};
class result//储存结果
{
public:
result()
{
}
result(const string &threadname, int ret)
: _threadname(threadname)
, _ret(ret)
{
}
void print()
{
cout << "I am " << _threadname << "added:" << _ret << endl;
}
private:
string _threadname;
int _ret;
};
class doTask//执行任务
{
public:
doTask()
{
}
doTask(int x, int y)
: _x(x), _y(y)
{
}
int run()
{
return _x + _y;
}
private:
int _x;
int _y;
};
class SentTask//发送任务
{
public:
SentTask()
{
}
SentTask(string &threadname, int x, int y)
: _threadname(threadname)
, dt(x, y)
{
}
int Exute()
{
return dt.run();
}
string threadname()
{
return _threadname;
}
private:
string _threadname;
doTask dt;
};
void *handler(void *args)
{
SentTask *pst = static_cast<SentTask *>(args);
result *ret = new result(pst->threadname(), pst->Exute());
delete pst;
return ret;
}
int main()
{
vector<pthread_t> tids;
for (int i = 0; i < sum; i++)
{
pthread_t tid;
char buffer[64];
snprintf(buffer, 64, "thread %d", i + 1);
string threadname = buffer;
SentTask *st = new SentTask(threadname, i, 3);
pthread_create(&tid, nullptr, handler, st);
tids.push_back(tid);
}
for (auto t : tids)
{
void *ret;
pthread_join(t, &ret);
result *tmp = static_cast<result *>(ret);
tmp->print();
delete tmp;
}
return 0;
}
5.补充
线程的优点
1.创建一个线程的代价比进程小的很多,创建一个线程就创建pcb,不用创建地址空间页表。
2.操作系统切换线程的工作,要比进程小的很多。如何理解呢?进程切换和线程切换相比,无非就是多了切换几个寄存器的工作呗,能有多大消耗呢?
但是别忘了还有缓存命中这种东西,cpu在加载代码的时候,会把该代码附近几行也加载到cpu的高速缓存中,一旦进程切换了,这些高速缓存的数据,对于下个进程就没有用了,都是失效的数据,但是对线程不是这样的,线程代码和全局数据是共享的。
线程的缺点
如果线程过多也是会有性能损失,因为线程来回切换也需要成本,在计算密集型应用线程数不要大于cpu核数。