Linux学习_多线程与互斥锁

本文介绍了Linux环境下多线程的基本概念,包括线程标识符`pthread_t`,线程创建`pthread_create`,线程参数传递,线程退出以及互斥锁和信号量的使用。通过示例代码展示了如何初始化和操作互斥锁以及信号量,用于保证线程安全地访问共享资源。
摘要由CSDN通过智能技术生成

基本概念

  1. 所谓线程,就是操作系统所能调度的最小单位。普通的进程,只有一个线程在执行对应的逻辑。
  2. 我们可以通过在程序中创建新线程,来令一个进程同时处理着多个线程
  3. 调度线程为单位,资源分配进程为单位
  4. 在使用多线程时,所有线程共用当前进程资源,且都可以访问到进程的全局变量
  5. 编译时要用gcc -o abc abc.c -lpthread即需要多加一个-lpthread
  6. void*代表了一种无类型的抽象指针,可以表示任意类型的指针,但是解引用之前要确认类型,如*(int*)tmp

线程的标识类型pthread_t

对于进程而言,每一个进程都有一个唯一对应的PID号来表示该进程
对于线程而言,每一个进程都有一个唯一对应的tid号来表示该进程,本质是一个pthread_t类型的变量
可以通过pthread_self()函数获取主线程的线程号,示例程序如下

#include <pthread.h>
#include <stdio.h>

int main()
{
	pthread_t tid = pthread_self();//获取主线程的 tid 号
	printf("tid = %lu\n",(unsigned long)tid);
	return 0;
}

线程的创建pthread_create

可以使用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指针,存放新创建的线程号
  2. const pthread_attr_t *attr表示线程的属性,可传入NULL表示默认属性
  3. void *(*start_routine) (void *)为一个void函数指针,表示线程执行的函数
  4. void *arg表示向执行函数传的参数,不传直接填NULL就行。
    示例程序如下
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>

void *fun(void *arg){
	printf("pthread_New = %lu\n",(unsigned long)pthread_self());//打印线程的 tid 号
}

int main(){
	pthread_t tid1;
	int ret = pthread_create(&tid1,NULL,fun,NULL);//创建线程
	if(ret != 0){                       //创建失败则ret返回值不为0(成功为0,失败为-1)
		printf("pthread_create err!\n");//创建失败则printf一个err
		return -1;
	}
	sleep(1);//主线程sleep一下
	return 0;
}

还可以一次性创建多个

pthread_t tid[3];
for(i = 0;i < 3;i++){
	int ret = pthread_create(&tid[i],NULL,fun,NULL);
}

线程竞争

  1. 使用多线程时,各个线程执行顺序随机竞争。
  2. 主线程结束后,进程直接结束,其余线程也立刻结束(无论执行状态)。
  3. 可以在主程序末尾return之前,加入sleep(n)函数使主线程稍作停顿,以完成所有其他线程的执行

线程的参数传入(单个)

这里有两种传入方式,引用传递或者强制类型转换
传入时,要记得先转成long再转成void*,因为针对不同位数机器,指针对应位数不同,int有时表示不了,会产生警告

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>

void *fun1(void *arg){
	printf("%s:arg = %d Addr = %p\n",__FUNCTION__,*(int *)arg,arg);
}
void *fun2(void *arg){
	printf("%s:arg = %d Addr = %p\n",__FUNCTION__,(int)(long)arg,arg);
}
int main(){
	int a = 50;
	pthread_t tid1,tid2;

	int ret = pthread_create(&tid1,NULL,fun1,(void *)&a);//创建新线程1,引用传递a的地址
	ret = pthread_create(&tid2,NULL,fun2,(void *)(long)a);//创建新线程2,强制类型转换,普通传入a的值
	printf("%s:a = %d Add = %p \n",__FUNCTION__,a,&a);//主线程也printf一下
	sleep(1);
	return 0;
}

执行结果:

main:a = 50 Add = 0x7ffe9c005b70
func1:arg = 50 Addr = 0x7ffe9c005b70
func2:arg = 50 Addr = 0x32

线程的参数传入(多个)

想传多个利用结构体就可以了,和传单个区别不大,就是把 &a 换成 &结构体名。如下:

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

struct Stu{
	int Id;
	char Name[32];
	float Mark;
};
void *fun1(void *arg){
	struct Stu *tmp = (struct Stu *)arg;
	printf("%s:Id = %d Name = %s Mark = %.2f\n",__FUNCTION__,tmp->Id,tmp->Name,tmp->Mark);
}
int main(){
	pthread_t tid1;
	struct Stu stu;
	stu.Id = 10000;
	strcpy(stu.Name,"ZhangSan");
	stu.Mark = 94.6;
	
	int ret = pthread_create(&tid1,NULL,fun1,(void *)&stu);
	printf("%s:Id = %d Name = %s Mark = %.2f\n",__FUNCTION__,stu.Id,stu.Name,stu.Mark);
	sleep(1);
	return 0;
}

线程的退出与回收

自己退出pthread_exit( )

#include <pthread.h>
void pthread_exit(void *retval);

用在执行函数fun中,表示结束本线程,并传一个数据给主线程,用指针retval传,不想传就NULL
此传回值,在fun函数中必须加上static作为前缀定义,如static int tmp,只定义为int tmp是传不回去的。原因是本身fun里面的参数是个临时变量,不加个static生命周期就结束了。

指定退出pthread_cancel( )

#include <pthread.h>
int pthread_cancel(线程id);

该函数输入参数为线程id,直接使该线程退出

阻塞回收pthread_join( )

#include <pthread.h>
int pthread_join(线程id, void **retval);

第一个参数代表要回收哪个线程,第二个参数代表接收变量
阻塞接收意思是死等,等收到返回值之后自己才返回,不然就一直卡在这一句死等。

非阻塞回收pthread_tryjoin_np( )

#define _GNU_SOURCE 
#include <pthread.h>
int pthread_tryjoin_np(线程id, void **retval);

不等,执行到这了立刻去看一眼,回收成功就返回0

互斥锁

由于多线程共享资源,有时会出现争抢全局变量等行为,导致哪个线程都得不出想要的结果,所以引入互斥锁
使用流程就是加锁->解锁->最后销毁

互斥量初始化pthread_mutex_init( )

int pthread_mutex_init(phtread_mutex_t *mutex, const pthread_mutexattr_t *restrict attr);//成功则返回0

其中的输入参数:

  1. phtread_mutex_t *mutex表示该互斥量指针,用于存放该互斥量
  2. const pthread_mutexattr_t *restrict attr表示控制互斥量的属性,一般填NULL就行

互斥量阻塞加锁pthread_mutex_lock( )与非阻塞加锁pthread_mutex_trylock( )

#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);

线程内执行lock后,其余线程运行到lock函数就会阻塞,直到unlock后其余线程再竞争,记得一定unlock不然死锁
阻塞与非阻塞区别:阻塞卡在lock这句硬要加,非阻塞就尝试一下加锁没加上就算了继续执行下面的了

互斥量解锁pthread_mutex_unlock( )

#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t *mutex);//成功则返回0

互斥量销毁pthread_mutex_destory( )

#include <pthread.h>
int pthread_mutex_destory(pthread_mutex_t *mutex);

示例程序

#define _GNU_SOURCE 
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>

pthread_mutex_t mutex;//互斥量变量 一般申请全局变量
int Num = 0;//全局变量

void *fun1(void *arg){
	pthread_mutex_lock(&mutex);//加锁 若有线程获得锁,则会阻塞
	执行点啥用上Num;
	pthread_mutex_unlock(&mutex);//解锁
	pthread_exit(NULL);//线程退出 pthread_join 会回收资源
}

void *fun2(void *arg){
	pthread_mutex_lock(&mutex);//加锁 若有线程获得锁,则会阻塞
	执行点啥用上Num;
	pthread_mutex_unlock(&mutex);//解锁
	pthread_exit(NULL);//线程退出 pthread_join 会回收资源
}

int main(){
	int ret;
	pthread_t tid1,tid2;
	ret = pthread_mutex_init(&mutex,NULL);//初始化互斥量
	ret = pthread_create(&tid1,NULL,fun1,NULL);//创建线程 1
	ret = pthread_create(&tid2,NULL,fun2,NULL);//创建线程 2
	pthread_join(tid1,NULL);//阻塞回收线程 1
	pthread_join(tid2,NULL);//阻塞回收线程 2
	pthread_mutex_destroy(&mutex);//销毁互斥量
	return 0;
}

信号量

信号量起通知作用,线程A 在等待某件事,线程 B 完成了这件事后就可以给线程A发信号,实现线程执行顺序可控

信号量初始化sem_init( )

int sem_init(sem_t *sem,int pshared,unsigned int value);

其中的输入参数:

  1. sem_t *sem表示传入一个sem_t指针,保存信号量
  2. int pshared0-线程控制、1-进程控制
  3. unsigned int value代表信号量初始值,0-阻塞、1-运行

信号量阻塞申请sem_wait( )与非阻塞申请sem_trywait( )

#include <pthread.h>
int sem_wait(sem_t *sem);//成功返回0
int sem_trywait(sem_t *sem);//成功返回0

检测当前信号量资源是否可用,可用则运行当前线程令信号量值-1(即从可用1改为阻塞0,不让其他线程用了)

信号量释放sem_post( )

#include <pthread.h>
int sem_post(sem_t *sem);

与申请相反,令信号量+1,即从阻塞0改为可用1

信号量销毁sem_destory( )

#include <pthread.h>
int sem_destory(sem_t *sem);

用完信号量最后要记得销毁掉哦,跟锁其实有点像的。
示例程序:

#define _GNU_SOURCE 
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <semaphore.h>

sem_t sem1,sem2,sem3;//申请的三个信号量变量

void *fun1(void *arg){
	sem_wait(&sem1);//因 sem1 本身有资源,所以不被阻塞 获取后 sem1-1=0 下次会会阻塞
	printf("%s:Pthread Come!\n",__FUNCTION__);
	sem_post(&sem2);// 使得 sem2 获取到资源
	pthread_exit(NULL);
}

void *fun2(void *arg){
	sem_wait(&sem2);//因 sem2 在初始化时无资源会被阻塞,直至 14 行代码执行 不被阻塞 sem2-1=0 下次会阻塞
	printf("%s:Pthread Come!\n",__FUNCTION__);
	sem_post(&sem3);// 使得 sem3 获取到资源
	pthread_exit(NULL);
}

void *fun3(void *arg){
	sem_wait(&sem3);//因 sem3 在初始化时无资源会被阻塞,直至 22 行代码执行 不被阻塞 sem3-1=0 下次会阻塞
	printf("%s:Pthread Come!\n",__FUNCTION__);
	sem_post(&sem1);// 使得 sem1 获取到资源
	pthread_exit(NULL);
}

int main(){
	int ret;
	pthread_t tid1,tid2,tid3;
	ret = sem_init(&sem1,0,1); //初始化信号量 1 并且使其可用
	ret = sem_init(&sem2,0,0); //初始化信号量 2 让其阻塞
	ret = sem_init(&sem3,0,0); //初始化信号 3 让其阻塞

	ret = pthread_create(&tid1,NULL,fun1,NULL);//创建线程 1
	ret = pthread_create(&tid2,NULL,fun2,NULL);//创建线程 2
	ret = pthread_create(&tid3,NULL,fun3,NULL);//创建线程 3

/*回收线程资源*/
	pthread_join(tid1,NULL);
	pthread_join(tid2,NULL);
	pthread_join(tid3,NULL);

/*销毁信号量*/
	sem_destroy(&sem1);
	sem_destroy(&sem2);
	sem_destroy(&sem3);
	return 0;
}

条件变量

类型:pthread_cond_t cond
初始化:int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);1放条件变量指针,2一般NULL
销毁:int pthread_cond_destroy(pthread_cond_t *cond);
使用:int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
示例:

pthread_mutex_lock(&g_tMutex);
pthread_cond_wait(&g_tConVar, &g_tMutex);// 条件不满足则unlock g_tMutex,条件满足则lock g_tMutex
pthread_mutex_unlock(&g_tMutex);
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值