对线程的控制思路和进程相似,创建、等待、终止,只需要调用接口就行。但是在Linux下没有线程的概念,因为Linux的设计者认为,线程是一种轻量级的进程,毕竟创建线程只需要创建PCB。因此Linux中使用多线程必须使用第三方pthread库,线程库为用户提供接口。
线程的创建——pthread_create
参数:
tread是线程标识符的指针,类似进程pid
attr是线程属性,一般是nullptr
start_routine是线程执行的函数
arg是传递给线程函数的参数
返回值:
线程的终止
线程终止有三种方式:
1.线程函数执行return,就会结束进程
2.线程函数使用pthread_exit接口
3.一个线程中使用pthread_cancel接口终止另一个进程
线程的等待——pthread_join
参数:
thread是线程标识符
retval是标识符对应线程退出后的返回值,是一个输出型参数。因为线程函数的返回值是void*类型,所以参数类型必须是void**
分离线程——pthread_detach
如果说线程的返回值我们不关心,使用join对操作系统是一种负担,但是不等待线程也会造成内存泄漏。使用这个接口就不用等待线程,在线程执行完自动回收。
分离线程既可以在其他线程分离,也可以自己分离
其他线程传入要分离的线程ID,自己分离调用pthread_self()获取线程tid即可
进程控制的例子:
#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>
using namespace std;
//使用线程实现从a到b的累加
class Request
{
public:
int _start;
int _end;
string _threadname;
Request(int start, int end, string name)
:_start(start)
,_end(end)
,_threadname(name)
{}
};
class Response
{
public:
int _val;
int _exitcode;
Response(int val, int exitcode)
:_val(val)
,_exitcode(exitcode)
{}
};
void* cal(void* arg)
{
Request* rq = (Request*)arg;
Response* rsp = new Response(0, 0);
for(int i = rq->_start; i <= rq->_end; i++)
{
usleep(100000);
rsp->_val += i;
cout << rq->_threadname << " pid:" << getpid() << " operate" <<": ret += " << i << endl;
}
//线程间共用堆,把主线程的数据释放
delete rq;
return rsp;
}
int main()
{
pthread_t tid;
Request* rq = new Request(0,50,"mythread");
//创建线程
cout << "main thread pid:" << getpid() << " create thread" << endl;
pthread_create(&tid, nullptr, cal, (void*)rq);
void* ret;
//等待线程,获取结果
pthread_join(tid, &ret);
Response* rsp = (Response*)ret;
cout << rq->_threadname <<" cal ret = " << rsp->_val << " exitcode = " << rsp->_exitcode << endl;
delete rsp;
return 0;
}
使用线程库,编译时要用-lpthread选项,声明使用的库
通过指令看到线程的PID相同,因为它们都是同一个进程的执行流资源,LWP是线程标识符,不同线程互不相同
线程ID
我们知道LInux系统没有线程概念,线程这个概念是由线程库来维护的,线程库调用了系统调用接口clone
clone是创建进程的接口(fork的底层也使用了它),线程库对其封装,提供可以创建线程的接口。那么,线程库必然会对建立的所有线程进行管理,就像操作系统管理进程一样,创建对应的TCB等等。
线程库是一个动态库,进程运行时会加载到共享区。库中就有线程对应的数据结构,这些数据结构都被存储到一个数组中,数组中每个线程的数据结构的地址就是它的tid
从上面的动态库结构看到:线程有自己独立的栈和局部存储。
线程栈
独立性
线程栈相互独立,也就是说每个线程即使使用了相同的线程函数,创建的变量也是互不相同的。
#include <iostream>
#include <pthread.h>
#include <vector>
#define NUM 4//线程数量
using namespace std;
void* fun(void* arg)
{
int val = 10;
return (void*)&val;//返回栈中变量地址
}
int main()
{
vector<pthread_t> tids;
//创建多个线程
for(int i = 0; i < NUM; i++)
{
pthread_t tid;
pthread_create(&tid, nullptr, fun, nullptr);
tids.push_back(tid);
}
//查看栈中变量地址
for(auto e : tids)
{
void* ret;
pthread_join(e, &ret);
cout << (int*)ret << endl;
}
return 0;
}
可见性
虽然栈是相互独立的,但是并不意味着栈中的数据对其他线程是不可访问的(实际应用中不推荐这种访问)
#include <iostream>
#include <pthread.h>
#include <vector>
#include <unistd.h>
using namespace std;
int* addr;
void* fun(void* arg)
{
int val = 0;
addr = &val;
int cnt = 10;
//循环打印val
while(cnt--)
{
sleep(1);
cout << "val:" << val <<endl;
}
return nullptr;
}
int main()
{
//创建线程
pthread_t tid;
pthread_create(&tid, nullptr, fun, nullptr);
//修改val
sleep(4);
cout << "main change val: 10" << endl;
*addr = 10;
pthread_join(tid, nullptr);
return 0;
}
线程局部存储
一个进程的全局变量对所有的线程都是可见的,如果想要一个线程独有的全局变量,可以使用线程局部存储。
在全局变量定义的前面加上 __thread
#include <iostream>
#include <pthread.h>
#include <vector>
#include <unistd.h>
using namespace std;
__thread int gval = 0;
void *fun1(void *arg)
{
gval += 100;
cout << &gval << ' ' << gval << endl;
return nullptr;
}
void *fun2(void *arg)
{
gval += 200;
cout << &gval << ' ' << gval << endl;
return nullptr;
}
int main()
{
vector<pthread_t> tids;
pthread_t tid;
pthread_create(&tid, nullptr, fun1, nullptr);
tids.push_back(tid);
pthread_create(&tid, nullptr, fun2, nullptr);
tids.push_back(tid);
for (auto e : tids)
{
pthread_join(e, nullptr);
}
return 0;
}