1、线程概念
1.1、线程和进程的对比
- 线程和进程类似,二者都有PCB
- 二者的底层函数都是一样的,都是使用到clone
- 进程可以变成线程
- 在Linux下,线程最是小的执行单位;进程是最小的分配资源单位
- .从内核里看进程和线程是一样的,都有各自不同的PCB,但是PCB中指向内存资源的三 级页表是相同的。
在下图中,A是一个进程,只有一个PCB,b他创建一个线程就多一个PCB,但是他们共享在一个资源。
1.2、线程之间 共享的资源
- 文件描述符表
- 每种信号的处理方式
- 当前工作目录
- 用户ID 和 组ID
- 内存地址空间
1.3、线程之间 不共享的资源
- 线程id
- 处理器现场和栈指针(内核栈)
- 独立的栈空间(用户空间栈)
- errno变量
- 信号屏蔽字
- 调度优先级
1.4、线程优缺点解析
1、优点:
- 提高程序的并发性
- 开销小,不用重新分配内存
- 通信和共享数据方便
2、缺点
- 线程不稳定(库函数实现)
- 线程调试比较困难(gdb支持不好)
- 线程无法使用unix经典事件,例如信号
1.5、线程的注意点
1、主线程一结束,子线程也会跟着结束(不管你子线程有没有执行完)。
2、线程之间,变量是共享的。
3、在创建函数中,错误不能用perror打印。
2、pthread解析
2.1察看LWP号
在终端输入ps -elf,就可以查看线程的LWP号
2.2 创建线程函数pthread_create
- 头文件:#include <pthread.h>
- 函数:int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
- 参数解析
- 参数1: pthread_t *thread,这里需要传递一个类型为pthread_t的变量,来保存创建出来新线程的ID
- 参数2:const pthread_attr_t *attr:线程属性设置,如使用默认属性,则传NULL。这里我们一般在写代码的时候都是使用NULL,设置成默认的。
- 参数3:void *(*start_routine) (void *):函数指针,指向新线程应该加载执行的函数模块。就是写这个线程被创建出来后要执行的函数。
- 参数4:void *arg:指定线程将要执行调用的那个函数的参数 (即这个参数是要传到,要执行函数里的)
- 返回值:成功返回0,失败返回错误号。(判断函数执行是否有错误不能用perror打印)
示例代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
//线程要执行的函数
void *run(void* arg)
{
int i = (int)arg;
printf("我是第%d个子线程\n",i+1);
}
int main(void)
{
//这里我创建pthread_t数组,来创建多个子线程
pthread_t tid[5];
//这个用于保存创建线程时产生的错误号
int err;
//创建线程
for(int i = 0;i<5;i++)
{
//创建线程,第二个参数传NULL,第三个传执行函数,参数4为传入执行函数的参数
//这里我传进去的i 要强转成 void* 才行
err = pthread_create(&tid[i],NULL,run,(void*)i);
//错误处理
if (err != 0)
{
//错误信息处理,方便显示到终端
fprintf(stderr, "can't create thread: %s\n", strerror(err));
exit(1);
}
}
sleep(i);
return 0;
}
注意点:
1、当子线程执行完指定的函数(就是第三个参数start_routine)之后,这个线程就退出了(这个线程就死了),其他线程可以使用pthread_join来获取start_routine函数的返回值,并且回收结束的线程(收尸操作)。
2、当创建线程函数pthread_create结束后新的ID会被写到第一个参数(pthread_t *thread)里,可以用pthread_self()函数获取。
2.2 pthread_self获取调用线程tid
- 头文件:#include <pthread.h>
- 函数:pthread_t pthread_self(void);
printf("In mian thread id = %u \n",pthread_self());
通过pthread_self函数就能获取id。每一个线程的id号都是不一样的。
2.3 pthread_exit线程退出函数
- 头文件:#include <pthread.h>
- 函数:void pthread_exit(void *retval);
- 参数: void *retval:线程退出时传递出的参数,可以是退出值或地址,如是地址时,不能是线程内部申请的局部地址。
- 注意点:
- 调用线程退出函数,注意和exit函数的区别,任何线程里exit导致进程退出(进程一旦退出就意味着说有的进程都终止退出)。其他线程 未工作结束,主控线程退出时不能return或exit。
- pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是 用malloc分配的,否则会内存出错!
2.4 pthread_join回收线程
- 头文件#include <pthread.h>
- 函数int pthread_join(pthread_t thread, void **retval);
- 参数解析
- 参数1:pthread_t thread:回收线程的tid
- 参数2:void **retval:接收退出线程传递出的返回值,一般这个参数我们都填空,只让这个函数起到回收进程的作用,如果需要用到该返回值,则看下面的注意点
- 返回值:成功返回0,失败返回错误号
- 注意:第二个参数的情况有很多种
- 如果thread线程通过return返回,retval所指向的单元里存放的是thread线程函数的返 回值。
- 如果thread线程被别的线程调用pthread_cancel异常终止掉,retval所指向的单元里存 放的是常数PTHREAD_CANCELED。
- 如果thread线程是自己调用pthread_exit终止的,retval所指向的单元存放的是传给 pthread_exit的参数。
- 如果对thread线程的终止状态不感兴趣,可以传NULL给retval参数。
2.4 pthread_cancel结束其他线程
- 头文件:#include <pthread.h>
- 函数:int pthread_cancel(pthread_t thread);
- 参数1:pthread_t thread:要结束的线程的tid
2.5 pthread_detach分离线程
- 头文件:#include <pthread.h>
- 函数:int pthread_detach(pthread_t tid);
- 参数1:pthread_t thread::分离线程tid
- 返回值:成功返回0,失败返回错误号。
解析:
- 该函数的作用和pthread_join类似,他的作用是线程也可以被置为detach状态,这样的线程一旦终止就立刻回收 它占用的所有资源,而不保留终止状态。
- 不能对一个已经处于detach状态的线程调用 pthread_join,这样的调用将返回EINVAL。(即,如果已经对一个线程调用了pthread_detach就不 能再调用pthread_join了)。
3、示例代码
3.1、c代码示例
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
int var = 100; //定义一个全局变量来验证变量共享
void *run(void* arg)
{
int i = (int)arg;
sleep(i);
if(i == 1) //第一个线程对变量修改
{
var =111;
printf("var = %d\n",var);
return var;
}
else if(i == 3) //第四个线程对变量进行修改
{
var =333;
printf("var = %d\n",var);
printf("我是第%d个线程,我结束了自己\n",i+1);
//结束自己 pthread_exit ,只要其他线程还在,你自杀后不能return或exit
pthread_exit((void *)var);
//pthread_detach(pthread_t tid)可以杀死其他线程
}
else
{
pthread_exit((void *)var);
}
return NULL;
}
int main(void)
{
pthread_t tid[5];
int *ret[5];
int i;
//看看主线程的线程id和进程ID
printf("In mian thread id = %u , pid = %u\n",pthread_self(),getpid());
//创建线程
for(i = 0;i<5;i++)
{
//创建线程,第二个参数传NULL,第三个传执行函数,参数4为传入执行函数的参数
pthread_create(&tid[i],NULL,run,(void*)i);
}
//回收多个子线程 pthread_join,不回收会造成僵尸线程
for(i = 0;i<5;i++)
{
//二级指针提供内存的读取和修改的,线程的返回值存在*ret中
pthread_join(tid[i],(void**)&ret[i]);
}
printf("in main id = %u, var = %d\n",pthread_self(),var);
sleep(i);
return 0;
}
3.2、C++封装线程
头文件
#ifndef BASETHRED_H
#define BASETHRED_H
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
class CBaseThread
{
public:
CBaseThread();
~CBaseThread();
//创建,并且启动线程
void start();
//自定义处理函数,这里定义成虚函数,每个子类的自己的run都不一样
virtual int run()=0;
private:
//线程类的处理函数,注意这里要设置成静态
static void* rountine(void *arg);
protected:
bool m_bRun; //运行标志位
bool m_bJoin; //是否回收
pthread_t m_tid; //线程ID
}
#endif
cpp文件
#include "CBase_pthread.h"
CBaseThread::CBaseThread()
:m_bRun(false),m_bJoin(false)
{
}
CBaseThread::~CBaseThread()
{
}
void CBaseThread::start()
{
//判断线程是否已经创建
if(m_bRun == false)
{
//我这里是把this(CBaseThread这个类的)传进去,要强转
if(pthread_create(&m_tid,NULL,rountine,(void*)this)!=0)
{
perror("create thread error:\n");
}
}
}
//处理子类的自定义函数
void* CBaseThread::rountine(void *arg)
{
//先把传进来的this给他强转回来,用这个thr接收
CBaseThread *thr = (CBaseThread*)arg;
if(thr->m_bJoin)
{
//自分离,不用调用join函数等待
//的线程一旦终止就立刻回收 它占用的所有资源,而不保留终止状态。
pthread_detach(pthread_self());
}
thr->m_bRun = true;
thr->run(); //用户自定义处理函数
thr->m_bJoin = false;
pthread_exit(NULL);
}
}
主函数
#include "CBase_pthread.h"
#include <iostream>
using namespace std;
int main(void)
{
CBaseThread my_Thread;
my_Thread.start();
sleep(1);
return 0;
}