linux线程同步

本文详细介绍了Linux线程的概念,包括线程与进程的区别、线程的共享资源和非共享资源,以及线程的优缺点。重点讲解了线程控制函数如pthread_self、pthread_create等,并介绍了线程同步的重要概念,如互斥量(mutex)、死锁、读写锁和条件变量,通过实例展示了它们的使用方法和场景。此外,还提到了线程同步中需要注意的事项和最佳实践。
摘要由CSDN通过智能技术生成

线程框架

一、线程概念

线程的概念:

轻量级的进程,一个进程内部可以有多个线程,默认情况下一个进程只有一个线程。linux下,线程是最小的执行单位,进程是最小的系统资源分配单位。

线程和进程的区别:

区别:在于是否共享地址空间。 独居(进程);合租(线程)。

进程:有独立的地址空间,拥有PCB

线程:也有PCB,但没有独立的地址空间(共享)

线程是公用进程的空间的,那么可以理解为,在内存上,除了栈(stack)这块,其他地方都是共享的!(下图所示),这是因为线程是有自己的执行目的的,每个线程的任务是非常明确的,后面会提到,一个线程实际上就是执行的一个函数,因为函数是存储在栈中的,所以这块是没法共享的,要进行区分,除此之外,其他区域都是可以共享的。

对于进程来说,相同的地址(同一个虚拟地址)在不同的进程中,反复使用而不冲突。原因是他们虽虚拟址一样,但,页目录、页表、物理页面各不相同。相同的虚拟址,映射到不同的物理页面内存单元,最终访问不同的物理页面。

但!线程不同!两个线程具有各自独立的PCB,但共享同一个页目录,也就共享同一个页表和物理页面。所以两个PCB共享一个地址空间。实际上,无论是创建进程的fork,还是创建线程的pthread_create,底层实现都是调用同一个内核函数clone。

如果复制对方的地址空间,那么就产出一个“进程”;如果共享对方的地址空间,就产生一个“线程”。

二、线程共享资源和非共享资源

线程共享资源:
  1. 文件描述符表
  2. 每种信号的处理方式
  3. 当前工作目录
  4. 用户ID和组ID
  5. 内存地址空间 (.text/.data/.bss/heap/共享库)
线程非共享资源:
  1. 线程id
  2. 处理器现场和栈指针(内核栈)
  3. 独立的栈空间(用户空间栈)
  4. errno变量(每个线程有自己独有的errno变量)
  5. 信号屏蔽字
  6. 调度优先级(可以设置线程的优先级)

三、线程的优缺点

优点:
  1. 提高程序并发性(为了更好的利用CPU)
  2. 开销小(不必再申请空间,直接用的是进程的空间)
  3. 数据通信、共享数据方便(在一个进程中的线程,可以共享进程中所创建的变量来共用)
缺点:
  1. 库函数,不稳定 (因为早期Unix并没有线程的概念,线程概念是后加的,所以,是放在了库函数中)
  2. 调试、编写困难
  3. 对信号支持不好

四、线程控制函数

获取线程ID pthread_self

功能:获取线程ID。其作用对应进程中 getpid() 函数。

pthread_t pthread_self(void); 
成功:返回线程ID 失败:无

线程ID:pthread_t类型,本质:在Linux下为无符号整数(%lu),其他系统中可能是结构体实现

线程ID是进程内部,识别标志。(两个进程间,线程ID允许相同)

注意:不应使用全局变量 pthread_t tid,在子线程中通过pthread_create传出参数来获取线程ID,而应使用pthread_self。

创建线程pthread_create

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine) (void *), void *arg);
thread 		线程的ID,传出参数
attr 		代表线程的属性,通常传NULL,表示使用线程默认属性
第三个参数  	函数指针, 函数形式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了)。如果任意一个线程调用了exit或_exit,则整个进程的所有线程都终止,由于从main函数return也相当于调用exit,为了防止新创建的线程还没有得到执行就终止,我们在main函数return之前延时1秒,这只是一种权宜之计,即使主线程等待1秒,内核也不一定会调度新创建的线程执行.

将上面代码中的sleep(1)换成如下语句即可:

pthread_exit(NULL);

线程退出函数pthread_exit

功能:将单个线程退出

void pthread_exit(void *retval);//参数:retval表示线程退出状态,通常传NULL

**参数:**retval表示线程退出状态,通常传NULL

注意:

线程中,禁止使用exit函数,会导致进程内所有线程全部退出。多线程环境中,应尽量少用,或者不使用exit函数,取而代之使用pthread_exit函数,将单个线程退出。任何线程里exit导致进程退出,其他线程未工作结束,主控线程退出时不能return或exit。

另注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。

线程回收pthread_join

功能:线程回收函数,阻塞等待线程退出,获取线程退出状态;其作用,对应进程中 waitpid() 函数。

int pthread_join(pthread_t thread, void **retval); 

参数:

  • threa——传出参数
  • retval ——存储线程结束状态(注意是void **)

进程中:main返回值、exit参数–>int;等待子进程结束 wait 函数参数–>int *

线程中:线程主函数返回值、pthread_exit–>void *;等待线程结束 pthread_join 函数参数–>void **

**函数返回值:**成功:0;失败:错误号

参数 retval 非空用法:

  1. 如果thread线程通过return返回,retval所指向的单元里存放的是thread线程函数的返回值。
  2. 如果thread线程被别的线程调用pthread_cancel异常终止掉,retval所指向的单元里存放的是常数PTHREAD_CANCELED。
  3. 如果thread线程是自己调用pthread_exit终止的,retval所指向的单元存放的是传给pthread_exit的参数。
  4. 如果对thread线程的终止状态不感兴趣,可以传NULL给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;  //注意void **retval,所以声明void *
	//线程回收函数
	pthread_join(tid, &ret);
	printf("ret exit with %d\n",(int)ret);
	pthread_exit(NULL);
	return 0;
}

输出结果:

线程分离pthread_detach

功能:实现线程分离

int pthread_detach(pthread_t thread);//成功:0;失败:错误号

线程分离状态:指定该状态,线程主动与主控线程断开关系。线程结束后,其退出状态不由其他线程获取,而直接自己自动释放。网络、多线程服务器常用。

进程若有该机制,将不会产生僵尸进程。僵尸进程的产生主要由于进程死后,大部分资源被释放,一点残留资源仍存于系统中,导致内核认为该进程仍存在。

也可使用 pthread_create函数参2(线程属性)来设置线程分离。
注意:

一般情况下,线程终止后,其终止状态一直保留到其它线程调用pthread_join获取它的状态为止。但是线程也可以被置为detach状态,这样的线程一旦终止就立刻回收它占用的所有资源,而不保留终止状态。不能对一个已经处于detach状态的线程调用pthread_join,这样的调用将返回EINVAL错误。也就是说,如果已经对一个线程调用了pthread_detach就不能再调用pthread_join了。
举例:

#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
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值