Linux基础——线程

1、线程概念

1.1、线程和进程的对比

  1. 线程和进程类似,二者都有PCB
  2. 二者的底层函数都是一样的,都是使用到clone
  3. 进程可以变成线程
  4. 在Linux下,线程最是小的执行单位;进程是最小的分配资源单位
  5. .从内核里看进程和线程是一样的,都有各自不同的PCB,但是PCB中指向内存资源的三 级页表是相同的。

在下图中,A是一个进程,只有一个PCB,b他创建一个线程就多一个PCB,但是他们共享在一个资源。
在这里插入图片描述

1.2、线程之间 共享的资源

  1. 文件描述符表
  2. 每种信号的处理方式
  3. 当前工作目录
  4. 用户ID 和 组ID
  5. 内存地址空间

1.3、线程之间 不共享的资源

  1. 线程id
  2. 处理器现场和栈指针(内核栈)
  3. 独立的栈空间(用户空间栈)
  4. errno变量
  5. 信号屏蔽字
  6. 调度优先级

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,失败返回错误号。

解析:

  1. 该函数的作用和pthread_join类似,他的作用是线程也可以被置为detach状态,这样的线程一旦终止就立刻回收 它占用的所有资源,而不保留终止状态。
  2. 不能对一个已经处于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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值