科林Linux5_线程

news2024/11/23 0:53:29

一、线程基础

进程是操作系统经典的执行任务的生产力。

进程是最小的资源分配单位,进程的内存开销较大,在内存资源不变的情况下,提高进程的执行能力(生产力)

线程寄存在进程中,与进程共享资源(内存),而后完成特定任务。

相比于传统的多进程并发程序,多线程开销更小,资源耗费更少,更加轻量级

1.1 线程如何分配系统资源

每一个进程都有一个内核对象(key),调度器根据key来分发时间片资源

用户级线程,是安装在用户层的。一些用户会在不支持线程机制的系统上安装用户线程,但是这类线程无法被系统识别和分配资源(时间片按进程数量分配)

多进程目的:得到更多时间片

主控线程(进程本身)是内核级线程,系统可分配内核对象,被CPU主动分发资源。内核线程可以实现物理级别的并行

sleep(0); 释放资源

用户线程不会被CPU分配时间片,但是可以使用主线程放弃的时间片(就近原则)。当多线程模型的某个线程陷入阻塞或挂起,其他线程可以接替资源继续执行,提高程序的执行速度,提高CPU的使用效率

多线程目的:更高的CPU访问频率

内核级线程的调度都有系统的参与,系统开销较大(上下文调度等)

用户级线程安装在用户空间,访问资源在用户层即可完成,无需内核干预。一般用户级线程都是以library第三方库的形式安装与使用的

AB型线程:为每个线程创建内核对象,可以被系统主动分配时间片,但是将线程安装在用户空间,大量的访问在用户空间即可完成。(只能在某些特定的系统上才支持)

1.2 进程的蜕变,如何区分进程和线程

进程是独占系统资源的

多线程:多个新的执行单元(普通线程)与原有执行单元(主控线程)共享存储空间

进程的蜕变:进程中自带的执行单元称为主控线程(main thread),其他称为普通线程(thread)

进程是最小的资源分配单位,线程是最小的调度单位,线程不额外分配内存资源,使用进程的

线程就是寄存器和栈:即可以使用cpu完成逻辑和运算,又可以保存恢复处理器现场,是系统中合格的调度单位。

1. 线程可以使用CPU,可以遵循分时复用原则使用时间片

2. 线程拥有独立的内核栈指针,可以保存自己的寄存器数据,可以保存恢复处理器现场。

1.3 线程间的共享资源

PCB、全局静态、堆(大部分系统)、库、代码段、文件描述符表是共享的

线程栈Thread_stack(8M)是非共享的,每个线程有自己独立的栈空间,从堆或者库空间分配

线程TCB是非共享的,保存线程信息(tid)

线程调度优先级是非共享的(控制时间片分配)

线程信号行为是共享的,某个线程改变信号行为,其他线程都共享此设置

每个线程都有自己独立的信号屏蔽字,是非共享的

二、线程的应用

2.1 线程命令

查看线程

ps -eLf    #查看所有线程
ps -Lf 进程id    #查看指定进程的线程

PID 进程id        LWP 轻量级进程编号        NLWP轻量级进程数量(线程数量)

Ubuntu系统“不支持"线程技术,内置NPTL(Nativ Posix Thread Libray)线程库,创建与使用的线程都是内核级

测试:若使用一个双核处理器处理一个进程下的两个用户级线程,那么CPU使用率只能达到50%,若处理两个内核级线程,则能达到100%

LWP为线程编号,是系统用来管理线程的。tid(unsigned long int)是线程的实际信息,一般打印线程tid是16进制

2.2 NPTL库函数

#include <pthread.h>

gcc pthread_create.c -lpthread -o app

2.2.1 线程创建pthred_create()

int err=pthread_create(&tid/*传出线程tid*/,
                       NULL/*线程属性,默认为NULL*/,
                       thread_job/*线程的任务地址,函数指针类型void* (*thread_job)(void*)*/,
                       NULL/*线程工作参数,当线程创建函数时,自行传递*/);
//成功返回0,失败返回错误号

 errno在多进程下是全局变量,出错时将errno设置为错误号,通过perror()查找errno对应的错误并输出。

多线程下errno自动变为局部变量,避免多线程异常。但线程拥有自己的错误处理方式,通过线程函数返回值返回错误号。

#include<string.h>
char* errmsg=sterror(err);    //参数为错误号,返回值为错误信息字符串。

32位操作系统可用的线程数量:381,进程用户空间余量(>2G)/线程栈大小(8M)

64位操作系统线程数量要多的多,因为用户空间更大

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

void* thread_task(void* arg){
	//普通线程代码任务
	int code=*(int*)arg;
	printf("普通线程:正在执行,参数%d\n",code);
	while(1)
		sleep(1);
	return NULL;
}

int main(){
	//主控线程的代码在main中
	pthread_t tid;
	int code=1024;
	int flag=0;
	int err;
	while(1){
		if((err=pthread_create(&tid,NULL,thread_task,(void*)&code))>0){
			printf("thread_create error:%s\n",strerror(err));
			exit(0);//进程退出
		}
		printf("thread number%d\n",++flag);
	}
	while(1)
		sleep(1);
}

2.2.2 返回线程tid pthread_self()

pthread_t tid=pthear_self();    //返回当前线程tid

主线程创建后传出的tid,与普通线程pthread_self()获取的tid值相等但是含义不同。主要取决于线程的有效性,pthread_self()获取的tid保证线程存活有效,但是主线程传出的tid指向的线程可能已经结束了

2.2.3 回收线程pthread_join()

* 间接引用,尝试对空间进行读写

阻塞回收指定线程,一次回收一个,可避免僵尸线程,并得到线程的返回值 

pthread_join(pthread_tid,void** reval);  
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<string.h>

void* thread_task(void* arg){
	//普通线程代码任务
	int code=*(int*)arg;
	printf("普通线程tid 0x%x:正在执行,参数%d\n",(unsigned int)pthread_self(),code);
	sleep(5);
	return (void*)8;//线程返回值
}

int main(){
	//主控线程的代码在main中
	pthread_t tid;
	void* reval;
	int code=1024;
	int err;
	if((err=pthread_create(&tid,NULL,thread_task,(void*)&code))>0){
		printf("thread_create error:%s\n",strerror(err));
		exit(0);//进程退出
	}
	printf("主控线程tid 0x%x,Create tid 0x%x\n",(unsigned int)pthread_self(),(unsigned int)tid);
	pthread_join(tid,&reval);
	printf("主线程回收成功,reval=%d\n",(int)reval);
	while(1)
		sleep(1);
}

2.2.4 杀死线程pthread_cancel()

pthread_cancel(pthread_t tid);    //杀死线程,取消线程

如果线程是正常退出,通过join可以获取返回值,若是被杀死的,返回值是-1

线程的返回值不允许使用-1,保留给cancel使用

信号处理的条件:系统调用、软件中断、异常,进程中的信号一定会被处理

cancel需要被取消的线程产生系统调用(调用系统函数),才能处理取消事件,否则无法处理,线程无法被杀死

pthread_testcancel();    //只会触发系统空调用,不会进行额外任务
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>

void* jobs(void* arg){
	while(1){
		pthread_testcancel();
	}
	return (void*)9;
}

int main(){
	pthread_t tid;
	pthread_create(&tid,NULL,jobs,NULL);
	sleep(5);

	//主线程杀死普通线程
	pthread_cancel(tid);
	void* reval;
	pthread_join(tid,&reval);
	printf("join sucess,thread return val %d\n",(int)reval);
	return 0;
}

2.2.5 线程两种退出状态

1、回收态线程(PTHREAD_JOINABLE),这类线程接收后必须通过join函数回收否则内存泄漏(默认)

2、分离态线程(PTHREAD_DETACH),这种线程结束后,系统自行回收线程资源,无需用户参与

可以将回收线程变为分离线程,但是此操作不可逆转,分离线程无法切换为回收

两种状态互斥,线程只能保有一种退出状态。不允许对分离态线程进行回收操作,回收操作会失败;不能对处于回收阶段的线程设置分离,设置分离不会成功。

pthread_detach(pthread_t tid);    //设置分离态函数 
pthread_join(pthread_t tid);    //join阻塞的线程处在回收阶段

join和detach函数一个成功,另一个失败

回收态:要自行回收所有线程,比较麻烦;避免内存泄露的同时,可以了解退出原因(return value)

分离态:系统自行回收所有资源;无法获取线程的返回值,不能判断线程的退出原因

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

//设置分离,两种状态互斥

void* jobs(void* arg){
	pthread_detach(pthread_self());//设置分离态
	printf("thread 0x%x running\n",(unsigned int)pthread_self());
	while(1)
		sleep(1);
	return (void*)6;
}

int main(){
	pthread_t tid;
	pthread_create(&tid,NULL,jobs,NULL);
	int err;

	sleep(0);//切换时间片

	if((err=pthread_join(tid,NULL))>0){//对普通线程进行回收
		printf("join failed:%s\n",strerror(err));
		exit(0);
	}
	while(1)
		sleep(1);
}

开始时间片在主线程上,join先运行,detach失效。使用sleep(0)切换时间片后,detach先运行,tid线程变成分离态,不可被回收,错误信息为无效参数

2.2.6 线程的退出方式

pthread_cancel(),只要知道目标tid,就可以杀死任意线程。即使杀死主线程,进程仍然存在

return,普通线程执行,结束当前线程;主线程执行,结束整个进程,杀死所有线程 

exit(0),杀死整个进程

pthread_exit(void*);    //退出当前线程,不会影响进程(主线程也可以使用)

线程的能力一致,普通线程可以创建、回收、取消其他线程,使用同一进程资源,只有时间早晚区别 

2.2.7 多线程开发模型

2.3 多进程模型与多线程模型的利弊

多进程模型:多进程开销更大(调度、内存)。

(Chrome)       稳定性好,每一个进程独立,一个进程工作异常不会影响其他进程或整个程序

多线程模型:开销小。

(FIrefox)        稳定性差,一个线程异常会导致整个进程退出。开发复杂,要保障线程安全

2.4 线程属性

系统支持自定义/客制化线程,在线程创建之前,就可以更改线程

struct pthread_attr_t{
    //线程的调度优先级 = 默认
    //线程的警戒缓冲区
    //线程的优先级指针
    //线程的退出状态 = 回收态
    //线程的栈地址
    //线程的栈大小 = 0 ->8M
}attr;

软件开发不调整优先级,以免影响系统资源分配。改变优先级可能会给系统造成不稳定因素

2.4.1 直接创建分离态线程

pthread_attr_int(pthread_attr_t* attr);    //初始化线程属性,初始化完毕为默认属性

pthread_attr_destory(pthread_attr_t* attr);    //销毁线程属性

pthread_attr_getdetachstate(pthread_attr_t* attr,int* detachstate);    
//获取属性中的退出状态,传出到detashstate变量中

pthread_attr_setdetachstate(pthread_attr_t* attr,
                            int detachstate/*PTHREAD_CREATE_JOINABLE|PTHREAD_CREATE_DETACHED);    
//设置修改属性中的退出状态

步骤:

(1)定义线程属性

(2)初始化线程属性(默认)

(3)修改线程属性

(4)使用自定义属性结构体创建线程

(5)销毁线程属性

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

void* jobs(void* arg){
	printf("running..\n");
	while(1)
		sleep(1);
}

int main(){
	pthread_t tid;
	//定义属性
	pthread_attr_t attr;
	//初始化属性
	pthread_attr_init(&attr);

	//获取查看属性中的退出状态
	int detachstate;
	pthread_attr_getdetachstate(&attr,&detachstate);
	if(detachstate==PTHREAD_CREATE_JOINABLE){
		printf("默认属性为 JOIN 回收态\n");
	}
	else{
		printf("默认属性为 DETACH 分离态\n");
	}
	pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
	//创建分离线程
	pthread_create(&tid,&attr,jobs,NULL);
	//回收
	int err;
	if((err=pthread_join(tid,NULL))>0){
		printf("join failed:%s\n",strerror(err));
		exit(0);
	}
	//销毁属性
	pthread_attr_destroy(&attr);
	return 0;
}

有些线程库函数不用-lpthread加载库也能编译通过,但是无法正常执行

通过pthread_detach()函数设置分离:多线程模型中对少量的线程通过函数设置分离

通过修改属性直接创建分离线程:大批量创建分离线程

2.4.2 提高线程创建数量(x86) 

pthread_attr_getstak(pthread_attr_t* attr,void** stackaddr,size_t* stacksize);
//从属性结构体种获取栈地址与大小传出

pthread_attr_setstak(pthread_attr_t* attr,void* stackaddr,size_t stacksize);
//将栈地址与大小设置到属性中

用户要修改线程栈,是需要自行申请空间作为栈内存的

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

void* jobs(void* arg){
	printf("running..\n");
	while(1)
		sleep(1);
}

int main(){
	pthread_t tid;
	//定义属性
	pthread_attr_t attr;
	//初始化属性
	pthread_attr_init(&attr);

	//查看默认属性中的栈信息
	void* stackaddr;
	size_t stacksize;
	pthread_attr_getstack(&attr,&stackaddr,&stacksize);
	printf("默认属性中,栈地址 %p,栈大小 %d\n",stackaddr,stacksize);
	//如何修改线程栈大小,提高线程数量
	stacksize=0x100000;//1M
	int err;
	int flag=0;
	while(1){
		if((stackaddr=(void*)malloc(stacksize))==NULL){
			perror("malloc failed");
			exit(0);
		}
		pthread_attr_setstack(&attr,stackaddr,stacksize);
		if((err=pthread_create(&tid,&attr,jobs,NULL))>0){
			printf("create error:%s\n",strerror(err));
			exit(0);
		}
		printf("t number %d\n",++flag);
	}

	//回收
	if((err=pthread_join(tid,NULL))>0){
		printf("join failed:%s\n",strerror(err));
		exit(0);
	}
	//销毁属性
	pthread_attr_destroy(&attr);
	return 0;
}

开发时不建议修改线程属性,改变线程属性会将线程变得不稳定

三、线程安全(互斥与同步)

3.1 互斥

多线程执行时,如果访问共享数据,可能会导致冲突和异常,开发者需要对其进行控制(互斥操作)

多线程访问同一个东西:

  1. IO访问:1.磁盘文件:如果多个线程对相同文件进行处理、引发文件数据异常
                   2.数据库:数据库操作,多线程同时读写访问、引发异常
  2. 多线程同时处理相同的全局数据,引发异常:多线程访问全局变量,计算产生无效的重叠。如果后续线程无法在前置线程的结果上累加,导致结果异常
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<string.h>

int code;

void* thread_job(void* arg){
	int temp;
	for(int i=0;i<5000;i++){
		temp=code;
		printf("thread 0x%x,++code=%d\n",(unsigned int)pthread_self(),++temp);
		code=temp;
	}
}

int main(){
	pthread_t tids[2];
	int i;
	for(i=0;i<2;i++)
		pthread_create(&tids[i],NULL,thread_job,NULL);
	while(i--)
		pthread_join(tids[i],NULL);
	printf("process done\n");
}


3.1.1 互斥锁技术

通过某种手段,让全局变量在一个时间内只有一个线程访问读写,避免数据访问异常

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;    //互斥锁静态初始化

使用互斥锁保护全局资源

临界区(互斥性)只有一个线程可以执行

惊群效应:在资源有限的情况下,让若干单位争抢资源,但是只有少数单位可以成功,其他参与单位产生的开销没有意义

通过分发唤醒标记的方式,决定下次谁来使用,避免惊群问题。得到标记的线程会被唤醒争抢资源,没有标记的线程持续挂起

就近原则:如果一个线程释放资源后立即申请资源,在这个线程有多余的时间片,大概率继续占用资源。因为它继续使用,使用效率好

pthread_mutex_init(&lock/*锁地址*/,NULL/*锁属性*/);    //互斥锁动态初始化
pthread_mutex_destroy(&lock);    //释放互斥锁
pthread_mutex_lock(&lock);    //阻塞上锁,如果无法获取则挂起等待
pthread_mutex_trylock(&lock);    //非阻塞请求锁,如果无法获取则立即返回
pthread_mutex_unlock(&lock);    //解锁

无论是锁还是受保护的资源,一般都为全局数据

要在读写访问全局资源的位置加锁,不同的上锁位置会影响线程的执行过程

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

int code;
pthread_mutex_t lock;

void* thread_job(void* arg){
	int temp;
	for(int i=0;i<5000;i++){
		pthread_mutex_lock(&lock);
		temp=code;
		printf("thread 0x%x,++code=%d\n",(unsigned int)pthread_self(),++temp);
		code=temp;
		pthread_mutex_unlock(&lock);
	}
}

int main(){
	pthread_t tids[2];
	pthread_mutex_init(&lock,NULL);
	int i;
	for(i=0;i<2;i++)
		pthread_create(&tids[i],NULL,thread_job,NULL);
	while(i--)
		pthread_join(tids[i],NULL);
	pthread_mutex_destroy(&lock);
	printf("process done\n");
}

3.1.2 读写锁

互斥锁的资源利用不充分,某些资源多个线程应该可以共享访问

读共享、写独占、读写互斥:虽然只允许一个线程修改,但是允许多个线程同时读访问,提高资源的利用率(提高读效率)

读称为共享锁(多把),写称为独占锁(互斥锁,一把)

pthread_rwlock_t lock = PTHREAD_RWLOCK_INITIALIZER;    //读写锁静态初始化

pthread_rwlock_init();    //读写锁动态初始化
pthread_rwlock_destroy();    //销毁读写锁
pthread_rwlock_rdlock();    //请求读锁
pthread_rwlock_wrlock();    //请求写锁
pthread_rwlock_unlock();    //解锁

在某些依赖共享数据的情况下,读写锁的使用率比互斥锁高

写锁被占用,其他线程申请会阻塞等待
读锁有数量限制,如果读锁被耗尽,新线程挂起等待,直到被释放

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

int code;
pthread_rwlock_t lock=PTHREAD_RWLOCK_INITIALIZER;

void* thread_read(void* arg){
	while(1){
		pthread_rwlock_rdlock(&lock);
		printf("read thread 0x%x,read code=%d\n",(unsigned int)pthread_self(),code);
		pthread_rwlock_unlock(&lock);
		usleep(200000);
	}
}

void* thread_write(void* arg){
	while(1){
		pthread_rwlock_wrlock(&lock);
		printf("write thread 0x%x,add code=%d\n",(unsigned int)pthread_self(),++code);
		pthread_rwlock_unlock(&lock);
		usleep(200000);
	}
}

int main(){
	pthread_t tids[8];
	int i;
	for(i=0;i<3;i++)
		pthread_create(&tids[i],NULL,thread_write,NULL);
	for(i;i<8;i++)
		pthread_create(&tids[i],NULL,thread_read,NULL);
	while(i--)
		pthread_join(tids[i],NULL);
	pthread_rwlock_destroy(&lock);
	printf("process done\n");
}

3.1.3 进程互斥锁

进程间共享数据,采用mmap共享映射

多进程访问共享数据,采用进程锁

(1)定义互斥锁

(2)定义互斥锁属性

pthread_mutexattr_t attr;

(3)初始化互斥锁属性

pthread_mutexattr_init(&attr);

(4)设置属性,从默认的线程互斥改为进程互斥

pthread_mutexattr_setpshared(&attr,
                            PTHREAD_PROCESS_SHARED/*进程锁,PTHREAD_PROCESS_PRIVATE线程锁*/);

(5)使用自定义锁属性初始化互斥锁

pthread_mutex_init(&lock,&attr);
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<sys/fcntl.h>
#include<sys/mman.h>
#include<sys/wait.h>

//进程共享数据
typedef struct{
	int code;
	pthread_mutex_t lock;
}shared_t;

int main(){
	shared_t* ptr=NULL;
	//初始化映射文件
	int fd;
    //touch Mapfile
	fd=open("Mapfile",O_RDWR);
	//截断处理
	ftruncate(fd,sizeof(shared_t));
	//映射
	ptr=mmap(NULL,sizeof(shared_t),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
	close(fd);

	//将互斥锁变为进程锁,初始化code
	pthread_mutexattr_t attr;
	pthread_mutexattr_init(&attr);
	pthread_mutexattr_setpshared(&attr,PTHREAD_PROCESS_SHARED);
	pthread_mutex_init(&ptr->lock,&attr);
	ptr->code=0;

	//进程创建
	pid_t pid;
	pid=fork();
	if(pid>0){
		for(int i=0;i<5000;i++){
			pthread_mutex_lock(&ptr->lock);
			printf("parent pid %d,++code:%d\n",getpid(),++(ptr->code));
			pthread_mutex_unlock(&ptr->lock);
		}
		wait(NULL);
	}
	else if(pid==0){	
		for(int i=0;i<5000;i++){
			pthread_mutex_lock(&ptr->lock);
			printf("child pid %d,++code:%d\n",getpid(),++(ptr->code));
			pthread_mutex_unlock(&ptr->lock);
		}
		exit(0);
	}
	else{
		perror("fork call failed");
		exit(0);
	}
	return 0;
}

3.1.4 旋转锁

等待时不挂起。互斥锁等待时挂起,进程常态为睡眠态;旋转锁的线程常态为运行态,时间片开销大,但是锁的利用率高

3.1.5 文件锁

文件读写锁,读共享,写独占,读写互斥:避免多线程访问文件冲突(内核是读写锁)

文件的使用需要修改文件属性,每个文件自带文件锁,用户需要修改文件属性,对文件进行上锁或解锁

struct flock{    //文件锁结构
    l_type = F_RDLCK|F_WELCF|F_UNLCK;    //锁:读锁|写锁|解锁
    l_whence = SEEK_SET|SEEJ_CUR|SEEK_EDN;    //上锁的绝对位置
    l_start = 0;    //上锁的相对位置,以绝对位置为基准
    l_len = 0;    //上锁的长度:>0锁指定长度,=0锁整个文件
    l_pid;    //占用当前文件锁的进程id
}lock;

修改替换文件锁结构体,实现对文件上读写锁效果:

(1)自定义文件锁结构体
(2)设置结构体成员
(3)替换文件原有的文件锁结构体

fcntl(fd,F_GETLK,struct flock* oldlock);    //获取某个文件的锁属性
fcntl(fd,F_SETLK,struct flock* newlock);    //非阻塞上锁,阻塞:F_SETLKW
//file1.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>

int main(){
	int fd=("Mapfile",O_RDWR);
	//对特定文件设置写锁
	struct flock lock;
	lock.l_type=F_WRLCK;
	lock.l_whence=SEEK_SET;
	lock.l_start=0;
	lock.l_len=0;

	//判断目标文件是否被占用
	struct flock olock;
	fcntl(fd,F_GETLK,&olock);
    if(olock.l_type==F_UNLCK){//文件处于解锁状态
		printf("file unlock\n");
		fcntl(fd,F_SETLKW,&lock);
		printf("file set wrlck sucess\n");
		sleep(10);
		lock.l_type=F_UNLCK;
		fcntl(fd,F_SETLKW,&lock);
		printf("file unlock sucess\n");
	}
	return 0;
}
//file2.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>

int main(){
	int fd=("Mapfile",O_RDWR);
	//对特定文件设置写锁
	struct flock lock;
	lock.l_type=F_WRLCK;
	lock.l_whence=SEEK_SET;
	lock.l_start=0;
	lock.l_len=0;

	//判断目标文件是否被占用
	fcntl(fd,F_SETLKW,&lock);
	printf("file set wrlck sucess\n");
	lock.l_type=F_UNLCK;
	fcntl(fd,F_SETLKW,&lock);
	printf("file unlock sucess\n");
	return 0;
}

Linux fcntl函数 建议锁 强制锁_fcntl 强制锁-CSDN博客 

3.2 死锁问题

如果多线程使用资源不当,导致死锁,会永久挂起线程,无法继续执行任务,死锁对于线程是灾难性的

多线程情况下共享资源有限,大多数情况下多线程要共享访问资源

死锁问题:线程永久挂起

杀死某个死锁线程,可以解除死锁

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

int codeA;
int codeB;

pthread_mutex_t lockA=PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t lockB=PTHREAD_MUTEX_INITIALIZER;

void* tA(void* arg){
	pthread_mutex_lock(&lockA);
	printf("tA 0x%x get lock A sucess.\n",(unsigned int)pthread_self());
	sleep(0);
	pthread_mutex_lock(&lockB);
	printf("tA 0x%x get lock B sucess.\n",(unsigned int)pthread_self());
	pthread_mutex_destroy(&lockB);
	pthread_mutex_destroy(&lockA);
}

void* tB(void* arg){
	pthread_mutex_lock(&lockB);
	printf("tB 0x%x get lock B sucess.\n",(unsigned int)pthread_self());
	sleep(0);
	pthread_mutex_lock(&lockA);
	printf("tB 0x%x get lock A sucess.\n",(unsigned int)pthread_self());
	pthread_mutex_destroy(&lockA);
	pthread_mutex_destroy(&lockB);
}

int main(){
	pthread_t tidA;
	pthread_t tidB;
	pthread_create(&tidA,NULL,tA,NULL);
	pthread_create(&tidB,NULL,tB,NULL);
	pthread_join(tidA,NULL);
	pthread_join(tidB,NULL);
	return 0;
}

死锁产生的条件(缺一不可):
1、互斥条件,资源只允许一个线程访问
2、请求与保持
3、不可剥夺,只能占用者自行释放
4、环路等待条件,某个资源多线程产生等待环路

通过非阻塞请求锁的方式避免死锁:若资源不能很好的分配,会产生大量的非阻塞请求开销

活锁:无法正确使用资源

任务具备随机性、不确定性

哲学家就餐问题:多线程在资源有限的情况下,如何有效利用资源

礼貌策略:当无法获取全部资源时,放弃占用的资源。若哲学家行为同步,导致无法进餐,活锁

权限机制:让某个哲学家拥有控制权限,可以随时进餐,但是超级哲学家只有一个。大多数时间超级哲学家独自进餐,资源被浪费

服务者模型:资源统计。服务者详细记录,餐桌资源占用情况,每个哲学家进餐前都要询问,可以避免死锁的情况下最大化利用资源

银行家算法:风险评估与统计方式,将每个锁资源抽象为银行资产,在避免倒闭的情况下,合理的分配资产,最大化利用资源

3.3 线程同步

多线程同步:线程控制,实现多线程并发执行时,步调一致,配合执行

条件变量:让多线程可以判断条件变量,来确定自身的行为。条件变量技术可以唤醒、挂起线程

有效的线程控制手段

pthread_cond_t cd = PTHREAD_COND_INITIALIZER;
pthread_cond_wait();    //首次执行,挂起当前线程并解锁互斥锁;被唤醒执行,上锁互斥锁

条件变量与互斥锁是绑定技术

pthread_cond_init(&cd,NULL);    //初始化
pthread_cond_destory(&cd);    //销毁
pthread_cond_wait(&cd,&lock);    //挂起
pthread_cond_signal(&cd);    //唤醒一个特点条件变量中的线程
pthread_cond_broadcast();    //唤醒所有线程

条件变量的数量:由工作条件数量决定,不同条件挂起的线程挂起在不同条件变量中,避免错误的唤醒

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1693780.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Leetcode 876. 链表的中间结点

题目描述 给你单链表的头结点 head &#xff0c;请你找出并返回链表的中间结点。 如果有两个中间结点&#xff0c;则返回第二个中间结点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[3,4,5] 解释&#xff1a;链表只有一个中间结点&#xff0c…

2024年【N1叉车司机】免费试题及N1叉车司机模拟试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 N1叉车司机免费试题考前必练&#xff01;安全生产模拟考试一点通每个月更新N1叉车司机模拟试题题目及答案&#xff01;多做几遍&#xff0c;其实通过N1叉车司机模拟考试题库很简单。 1、【多选题】《中华人民共和国特…

Spring AMQP 随笔 8 Retry MessageRecoverer ErrorHandler

0. 列位&#xff0c;响应式布局好麻烦的 … 有意思的&#xff0c;chrome devtool 在调试响应式的分辨率的时候&#xff0c;比如说在 宽度远远大于 768 的时候&#xff0c;按说浏览器也知道大概率是 web端方式打开&#xff0c;样式也是如此渲染&#xff0c;但一些事件(没有鼠标…

题解:P9535 [YsOI2023] 连通图计数

题意 求&#xff1a;在所有 n n n 个点 m m m 条边的无向简单连通图中&#xff0c;满足把第 i i i 个点删去后图被分为 a i a_i ai​​ 个连通块。 n − 1 ≤ m ≤ n 1 n-1\le m\le n1 n−1≤m≤n1。 思路 将 m n − 1 , m n , m n 1 mn-1,mn,mn1 mn−1,mn,mn1​ 三…

二叉树——堆详解

目录 前言&#xff1a; 一、堆的结构 二、向上调整和向下调整 2.1 向上调整 2.2 向下调整 2.3 向上调整和向下调整时间复杂度比较 三、堆的实现 3.1 堆的初始化 3.2 堆的销毁 3.3 堆的插入 3.4堆的删除 3.5 取堆顶元素 3.6 对堆判空 四、堆排序 五、TOP-K 问题 六、代码总…

电商公司需不需要建数字档案室呢

建立数字档案室对于电商公司来说是非常有必要的。以下是一些原因&#xff1a; 1. 空间节约&#xff1a;数字档案室可以将纸质文件转化为电子文件&#xff0c;节省了大量存储空间。这对于电商公司来说尤为重要&#xff0c;因为他们通常会有大量的订单、客户信息和供应商合同等文…

Python01:初入Python(Mac)

Python环境准备 下载Python&#xff1a;官网https://www.python.org/ 下载PyCharm&#xff1a;官网https://www.jetbrains.com/pycharm/download Python与PyCharm的关系 Python&#xff08;解释器&#xff09;&#xff1a;机器语言—>翻译人员–>翻译成电脑能读懂的 PyC…

DatePicker日期选择框(antd-design组件库)简单使用

1.DatePicker日期选择框 输入或选择日期的控件。 2.何时使用 当用户需要输入一个日期&#xff0c;可以点击标准输入框&#xff0c;弹出日期面板进行选择。 组件代码来自&#xff1a; 日期选择框 DatePicker - Ant Design 3.本地验证前的准备 参考文章【react项目antd组件-demo:…

2022蓝桥杯大赛软件类国赛Java大学B组 左移右移 空间换时间+双指针

import java.util.Scanner;public class Main {static Scanner scnew Scanner(System.in);public static void main(String[] args) {int nsc.nextInt();//数组长度int tsc.nextInt();//操作次数int arr[]new int[n];char arr1[] new char[t];int arr2[] new int[t];int vis…

金融信贷风控系统设计模式应用之模版方法

背景介绍 风控系统每种场景 如个人消费贷 都需要跑很多规则 规则1 申请人姓名身份证号实名验证规则2 申请人手机号码实名认证规则3 银行卡预留手机号码实名认证规则4 申请人银行卡预留手机号码在网状态检验规则5 申请人银行借记卡有效性核验规则6 户籍地址与身份证号归属地比…

后量子密码解决方案

什么是后量子密码学 (PQC)&#xff0c;为什么准备工作如此重要? 量子计算正在迅速发展;用不了多久&#xff0c;量子网络攻击就会成为可能。量子网络攻击将能够在几分钟内瘫痪大型网络。我们今天赖以保护我们的连接和交易的一切都将受到量子计算机的威胁&#xff0c;危及所有密…

Django中model中的抽象类

Django中model中的抽象类 当我们在app中models.py文件中定义model表并执行python manage.py makemigrations和python manage.py migrate后&#xff0c;Django就会在数据库中创建表 但是我们也可以对其默认配置修改&#xff0c;定义model类但是不在数据库中创建 from django.…

Behind the Code:Polkadot 如何重塑 Web3 未来

2024 年 5 月 17 日 Polkadot 生态 Behind the Code 第二季第一集 《创造 Web3 的未来》正式上线。第一集深入探讨了 Polkadot 和 Web3 技术在解决数字身份、数据所有权和去中心化治理方面的巨大潜力。 &#x1f50d; 查看完整视频&#xff1a; https://youtu.be/_gP-M5nUidc?…

基于Python对评论进行情感分析可视化

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景与意义 在当今数字化时代&#xff0c;用户生成内容&#xff08;UGC&#xff09;如在线评论、社交媒体…

需求响应+配网重构!含高比例新能源和用户需求响应的配电网重构程序代码!

前言 配电网重构作为配电网优化运行的手段之一&#xff0c;通过改变配电网的拓扑结构&#xff0c;以达到降低网损、改善电压分布、提升系统的可靠性与经济性等目的。近年来&#xff0c;随着全球能源消耗快速增长以及环境的日趋恶化&#xff0c;清洁能源飞速发展&#xff0c;分…

线性回归模型

目录 1.概述 2.线性回归模型的定义 3.线性回归模型的优缺点 4.线性回归模型的应用场景 5.线性回归模型的未来展望 6.小结 1.概述 线性回归是一种广泛应用于统计学和机器学习的技术&#xff0c;用于研究两个或多个变量之间的线性关系。在本文中&#xff0c;我们将深入探讨…

GM Bali,OKLink受邀参与Polygon AggIsland大会

5月16日-17日&#xff0c;OKLink 受到生态合作伙伴 Polygon 的特别邀请&#xff0c;来到巴厘岛参与以 AggIsland 为主题的大会活动并发表演讲&#xff0c;详细介绍 OKLink 为 Polygon 所带来的包括多个浏览器和数据解析等方面的成果&#xff0c;并与 Polygon 一起&#xff0c;对…

【maven与tomcat配置】如何正确配置maven及tomcat环境变量及运行Java项目 (附图文说明及下载包)

maven及tomcat配置详解 &#x1f354;涉及知识&#x1f964;写在前面&#x1f367;一、maven和tomcat是啥&#xff1f;&#x1f367;二、maven环境变量配置2.1获取maven包2.2创建本地仓库及修改配置A&#xff0e;校验是否安装javaB&#xff0e;创建本地maven存放仓库C&#xff…

C++vector的简单模拟实现

文章目录 目录 文章目录 前言 一、vector使用时的注意事项 1.typedef的类型 2.vector不是string 3.vector 4.算法sort 二、vector的实现 1.通过源码进行猜测vector的结构 2.初步vector的构建 2.1 成员变量 2.2成员函数 2.2.1尾插和扩容 2.2.2operator[] 2.2.3 迭代器 2…

OpenHarmony系统使用gdb调试init

前言 OpenAtom OpenHarmony&#xff08;简称“OpenHarmony”&#xff09;适配新的开发板时&#xff0c;启动流程init大概率会出现问题&#xff0c;其为内核直接拉起的第一个用户态进程&#xff0c;问题定位手段只能依赖代码走读和增加调试打印&#xff0c;初始化过程中系统崩溃…