线程的概念、优缺点和创建步骤

在这里插入图片描述

1. 什么是线程

线程的概念:轻量级的进程,一个进程内部可以有多个线程,默认情况下一个进程只有一个线程
线程是最小的执行单位,进程是最小的系统资源分配单位
  线程就是轻量级的进程,如果说下图中的a.out,是一个进程,那里面的那几根"弯弯曲曲"的线,就是所谓的线程,一般来说,一个进程中只有一个线程,当然,一个进程中也可以有多个线程,这种状况就叫做多线程,当然从图上也可以看出来,一个进程中的多个线程,也是公用的相同的资源的:
在这里插入图片描述
  因为线程是公用进程的空间的,那么可以理解为,在内存上,除了栈(stack)这块,其他地方都是共享的!(上图的左侧所示),这是因为线程是有自己的执行目的的,每个线程的任务是非常明确的,后面会提到,一个线程实际上就是执行的一个函数,因为函数是存储在栈中的,所以这块是没法共享的,要进行区分,除此之外,其他区域都是可以共享的。

  线程的好处也就可以看出来了,线程都在一个进程内部,随便一个变量所有线程都可以用;线程可以更有效的理由CPU(但是,要说的是,如果电脑只有一块CPU,一个核心数,线程再多也没办法同时"干活儿",因为在一个进程中,只能有一个“线程”在运行的(可以把线程理解成一个函数)),总之,多线程和多进程,都是为了更充分的利用CPU。

  那么再做个关于"进程"和"线程"的形象的比喻,一个工厂,但可供的电量有限,只能供应三个车间,错开开工,不能同时开工,那么这个工厂,就好比是cpu,这三个车间,可以理解成"进程",那么车间里面的每个人(苦力,真正干活的),就相当于"线程"。那么每个"线程"都有非常明确的分工。在车间里的设备和资源,对每个人(线程)来说也都是公有的。
在这里插入图片描述
  线程是最小的执行单位,进程是最小的系统资源分配单位 ,从内核角度看,进程和线程是没有区分的,内核实现都是通过 clone 函数实现的,线程也有自己的PCB

2. 线程共享资源与非共享资源

尽量不要让线程与信号放在一起使用,避免乱上加乱
线程共享资源:

  1. 文件描述符表
  2. 每种信号的处理方式
  3. 当前工作目录
  4. 用户ID和组ID
  5. 内存地址空间 (.text/.data/.bss/heap/共享库)

线程非共享资源:

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

每个线程有自己的errno

通过如下函数可以获得错误码对应的错误信息

char *strerror(int errnum);

3. 线程的优缺点

优点:

  1. 提高程序并发性(为了更好的利用CPU)
  2. 开销小(不必再申请空间,直接用的是进程的空间)
  3. 数据通信、共享数据方便(在一个进程中的线程,可以共享进程中所创建的变量来共用)

缺点:

  1. 库函数,不稳定 (因为早期Unix并没有线程的概念,线程概念是后加的,所以,是放在了库函数中)
  2. 调试、编写困难(可以让程序员后天学习克服)
  3. 对信号支持不好(大不了就不用信号了)

汇总:优点相对突出,缺点均不是硬伤。Linux下由于实现方法导致进程、线程差别不是很大。

4. 创建一个线程

int pthread_create(	pthread_t *thread, 
					const pthread_attr_t *attr,
					void *(*start_routine) (void *), 
					void *arg);

在这里插入图片描述

thread 		线程的ID,传出参数
attr 		代表线程的属性
第三个参数  	函数指针, void *func(void*)
arg 		线程执行函数的参数
返回值  		成功  0   失败  errno

编译时需要加 -lpthread
注意:线程ID在进程内是唯一的,但是在整个操作系统内部不一定是唯一的。

#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
 
void *thr(void* arg){
	//无符号长整型数  %lu
	printf("I am a thread! pid=%d, tid=%lu\n", getpid(), pthread_self());
}
 
int main(){
	pthread_t tid;
	pthread_create(&tid, NULL, thr, NULL);
	printf("I am main thread, pid=%d, tid=%lu\n", getpid(), pthread_self());
	sleep(1);
	return 0;
}

在这里插入图片描述上面的代码存在一个问题:
就是如果主线程不睡眠,则另一个线程没有机会去执行(因为主线程打印之后就直接执行return 0了)。
在这里插入图片描述
将上面代码中的sleep(1)换成如下语句即可:

pthread_exit(NULL);

4.1 线程退出函数pthread_exit

在这里插入图片描述注意事项:

  • 线程中使用pthread_exit 来退出线程
  • 线程中可以用 return(主控线程不行)
  • exit代表退出整个进程

4.2 线程回收pthread_join

线程回收函数,阻塞等待

int pthread_join(pthread_t thread, void **retval);
thread 		创建的时候传出的第一个参数
retval 		代表的传出线程的退出信息(就是返回值)
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
 
void *thr(void *arg){
	printf("I am a thread, tid=%lu\n", pthread_self());
	sleep(5);
	printf("I am a thread, tid=%lu\n", pthread_self());
	return (void*)100;
}
 
int main(){
	pthread_t tid;
	pthread_create(&tid, NULL, thr, NULL);
	void *ret;
	//线程回收函数
	pthread_join(tid, &ret);
	printf("ret exit with %d\n",(int)ret);
	pthread_exit(NULL);
	return 0;
}

在这里插入图片描述

4.3 杀死线程pthread_cancel

在这里插入图片描述

#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
 
void *thr(void*arg){
	while(1){
		printf("I am thread, very happy! tid=%lu\n", pthread_self());
		sleep(1);
	}
	return NULL;
}
int main(){
	pthread_t tid;
	pthread_create(&tid, NULL, thr, NULL);
	sleep(5);
	//杀死线程
	pthread_cancel(tid);
	void *ret;
	//阻塞等待回收线程
	pthread_join(tid, &ret);
	printf("thread exit with %d\n", ret);
	return 0;
}

在这里插入图片描述如果把上面的代码中的thr函数中的printf动作和sleep动作注释之后,线程就杀不死了。因为pthread_cancle函数需要有一个取消点
如果你的线程函数里面实在是没有取消点,你可以加上这样的一个函数:

pthread_testcancel();

通过这个函数可以强行添加一个取消点

4.4 线程分离pthread_detach

在这里插入图片描述

#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#include<string.h>
 
void *thr(void *arg){
	printf("I am a thread, self=%lu\n", pthread_self());
	sleep(4);
	printf("I am a thread, self=%lu\n", pthread_self());
	return NULL;
}
 
int main(){
	pthread_t tid;
	pthread_create(&tid, NULL, thr, NULL);
	//线程分离
	pthread_detach(tid);
	sleep(5);
	int ret=0;
	//阻塞失败
	if((ret=pthread_join(tid, NULL))>0){
		printf("join err:%d, %s\n", ret, strerror(ret));
	}
 
	return 0;
}

在这里插入图片描述执行了线程分离之后,pthread_join函数就回收失败了。

4.5 判断两线程ID是否相等pthread_equal

在这里插入图片描述

4.6 线程属性设置分离

创建那种易产生就直接是分离状的线程,不需要我们去执行detach函数来进程线程分离(因为执行detach函数还会有一些特殊情况,比如线程创建好之后很快就结束了,此时还没执行到detach函数)
在这里插入图片描述
在这里插入图片描述

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
 
void *thr(void *arg) {
    printf("I am a thread \n");
    return NULL;
}
 
int main() {
	//设置线程属性
    pthread_attr_t attr;
	//初始化属性
    pthread_attr_init(&attr);
    //设置线程分离属性,这样线程创建好之后就直接分开了
    pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);//设置属性分离
    pthread_t tid;
	//创建线程 第二个参数是线程属性
    pthread_create(&tid,&attr,thr,NULL);
    int ret;    
	//阻塞回收线程失败
    if((ret = pthread_join(tid,NULL)) > 0){
        printf("join err:%d,%s\n",ret,strerror(ret));
    }
	//摧毁属性
    pthread_attr_destroy(&attr);
    return 0;
}

4.7 创建多个子线程

#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
 
void *thr(void *arg){
	int num=(int)arg;
	printf("I am %d thread, self=%lu\n", num, pthread_self());
	return (void*)(100+num);
}
 
int main(){
	pthread_t tid[5];
	for(int i=0; i<5; i++){
		pthread_create(&tid[i], NULL, thr, (void*)i);
	}
	for(int i=0; i<5; i++){
		void *ret;
		pthread_join(tid[i], &ret);
		printf("i =%d, ret=%d\n", i, (int)ret);
	}
	return 0;
}

在这里插入图片描述

5. 线程使用注意事项

  1. 主线程退出其他线程不退出,主线程应调用pthread_exit。
  2. 避免僵尸进程
    pthread_join
    pthread_detach
    pthread_create指定分离属性
    被join线程可能在join函数返回前就释放完自己的所有内存资源,所以不应当返回被回收线程栈中的值。
  3. malloc和mmap申请的内存可以被其他线程释放。
  4. 应避免在多线程模型中调用fork,除非马上exec,子进程中只有调用fork的线程存在,其他线程在子进程中均pthread_exit
  5. 信号的复杂语义很难和多线程共存,应避免在多线程引入信号机制。
  6. 编译要指定-lpthread
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
进程和线程操作系统中的两个重要概念,它们都是用来实现并发执行的方式。它们各自有不同的优点和缺点。 进程的优点: 1. 独立性:每个进程都有独立的内存空间,进程之间相互隔离,一个进程的崩溃不会影响其他进程。 2. 稳定性:进程之间的通信通过操作系统提供的机制进行,可以保证数据的安全性和稳定性。 3. 多任务处理:操作系统可以同时运行多个进程,提高了系统的并发性和处理能力。 进程的缺点: 1. 资源消耗:每个进程都需要独立的内存空间和系统资源,创建和切换进程会消耗较多的资源。 2. 切换开销:由于进程切换需要保存和恢复进程的上下文,所以切换开销较大。 3. 同步与通信:不同进程之间的数据共享和通信比较复杂,需要使用操作系统提供的机制进行同步和通信。 线程的优点: 1. 轻量级:线程是进程内的执行单元,相比于进程,线程创建和切换开销较小。 2. 共享资源:线程可以共享进程的内存空间和系统资源,方便数据共享和通信。 3. 响应性:线程可以提高程序的响应速度,特别是在需要同时处理多个任务的情况下。 线程的缺点: 1. 安全性:由于线程共享进程的内存空间,多个线程同时访问共享数据可能会引发竞态条件和死锁等问题。 2. 稳定性:一个线程的崩溃可能会导致整个进程的崩溃。 3. 调试困难:由于线程共享进程的资源,线程之间的错误和调试比较困难。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值