科林Linux5_线程

一、线程基础

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

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

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

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

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();    //唤醒所有线程

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

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

泛型...

定义&#xff1a;在编译过程中约束操作的数据类型。&#xff08;统一数据类型&#xff09; 格式&#xff1a;<数据类型> 泛型中不能写基本数据类型。 泛型类 在一个类中&#xff0c;某个变量的数据类型不确定时&#xff0c;可以定义带有泛型的类。 泛型的底层是Obje…

Java 泛型基础

目录 1. 为什么使用泛型 2. 泛型的使用方式 2.1. 泛型类 2.2. 泛型接口 2.3. 泛型方法 3. 泛型涉及的符号 3.1. 类型通配符"?" 3.2. 占位符 T/K/V/E 3.3. 占位符T和通配符&#xff1f;的区别。 4. 泛型不变性 5. 泛型编译时擦除 1. 为什么使用泛型 Java 为…

Pandas 模块-操纵数据(12)-处理字符串数据

目录 1. .str 模块 1.1 数据准备 1.2 .str 函数详解 1.2.1 .str capitalize() 首字母大写 1.2.2 .str casefold() 返回字符串的副本 1.2.3 .str cat() 连接输出 1.2.4 .str center(width[,fillchar]) 字符串居中 1.2.5 .str contains() 含有特定字符 1.2.6 .str count() 计…

【代码随想录——回溯算法二周目】

1. 组合总和 var (path []intres [][]int )func combinationSum(candidates []int, target int) [][]int {path make([]int, 0)res make([][]int, 0)dfs(candidates,target,0,0)return res }func dfs(candidates []int, target int,tempTarget int,start int) {if tempTarg…

【Xilinx】常用的全局时钟资源相关Xilinx器件原语

1 概述 常用的与全局时钟资源相关的Xilinx器件原语包括&#xff1a; IBUFGIBUFGDS、OBUFGDS 和 IBUFDS、OBUFDSBUFGBUFGPBUFGCEBUFGMUXBUFGDLLIBUFDS_GTXE1IBUFDS_GTE2IBUFDS_GTE3OBUFDS_GTE3IBUFDS_GTE4OBUFDS_GTE4DCM 刚开始看到这写源语&#xff0c;免不了好奇这些源语对应的…

网络空间安全数学基础·群

重点&#xff1a; 1. 群及子群的定义及相关结论 2. 群的判断,子群的判断 3. 群的阶,元素的阶,它们的相互关系 4. 同态,同构,核子群 2.1群的定义 定义&#xff1a;设G是一非空集合。如果在G上定义了一个代数运算&#xff0c;称为乘法&#xff0c;记为ab&#xff0c;而且这个运…

Ubuntu18.04 OpenSSH升级

升级前版本&#xff1a; rootecs-m2eqyb:/opt# ll total 20912 drwxr-xr-x 2 root root 4096 May 10 16:23 ./ drwxr-xr-x 24 root root 4096 May 10 14:38 ../ -rw-r--r-- 1 root root 1848766 May 10 16:23 openssh-9.7p1.tar.gz -rw-r--r-- 1 root root 18038…

程序包org.springframework.boot不存在

springBoot项目启动报错 程序包org.springframework.boot不存在 1、检查依赖 首先检查pom文件判断依赖是否存在 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId><version>2.4.5…

二维前缀和

我们计算一维前缀和时的得心应手&#xff0c;但是到二维前缀和就有点力不从心了&#xff0c;这里总结了一下规律&#xff1a; 计算二维前缀和时我喜欢从下标为1的时候开始&#xff1a; per[i][j]per[i][j-1]per[i-1][j]-per[i-1][j-1]a[i][j]; i表示行&#xff0c;j表示列,i和…

嵌入式进阶——舵机控制PWM

&#x1f3ac; 秋野酱&#xff1a;《个人主页》 &#x1f525; 个人专栏:《Java专栏》《Python专栏》 ⛺️心若有所向往,何惧道阻且长 文章目录 舵机信号线代码示例初始化PWM初始化UART打印日志初始化外部中断Extimain函数 舵机最早用于船舶上实现转向功能,由于可以通过程序连…

Go使用结构体实现类(面向对象)

前置 package main ​ import ("fmt" ) ​ // 矩形结构体 type Rectangle struct {Length intWidth int } ​ // 计算矩形面积 func (r *Rectangle) Area() int {return r.Length * r.Width } ​ func main() {r : Rectangle{4, 2}// 调用 Area() 方法&#xff0c;计…

BUUCTF-WEB3

[极客大挑战 2019]Knife1 1.打开附件链接 一句话木马eval($_POST["Syc"]); 2.中国蚁剑 用中国蚁剑连接 在根目录下找到一个名为flag的文件 3.得到flag [极客大挑战 2019]Upload1

gcc g++不同版本切换命令

sudo update-alternatives --config g sudo update-alternatives --config gcc ubuntu20.04 切换 gcc/g 版本_ubuntu降低g版本-CSDN博客

Python零基础-中【详细】

接上篇继续&#xff1a; Python零基础-上【详细】-CSDN博客 目录 十、函数式编程 1、匿名函数lambda表达式 &#xff08;1&#xff09;匿名函数理解 &#xff08;2&#xff09;lambda表达式的基本格式 &#xff08;3&#xff09;lambda表达式的使用场景 &#xff08;4&…

Linux -- 进程间通信的五种方式

IPC&#xff08;InterProcess Communication&#xff09;的方式通常有管道&#xff08;包括无名管道和命名管道&#xff09;、消息队列、信号量、共享存储、Socket、Streams等。其中Socket和Stream支持不同主机上的两个进程IPC。 管道&#xff08;Pipes&#xff09;&#xff1a…

【数据库】基于PyMySQL连接并使用数据库(代码示例)

这里写目录标题 前言1、安装PyMySQL2、打开要连接的数据库3、创建数据库连接4、获取数据库版本5、新建数据库表6、向表中插入数据7、查询表中的相关记录8、更新表中的相关记录9、删除表中的相关记录10、关闭游标和连接完整代码 前言 本文演示了如何基于PyMySQL使用代码来创建数…

线性模型--普通最小二乘法

线性模型 一、模型介绍二、用于回归的线性模型2.1 线性回归&#xff08;普通最小二乘法&#xff09; 一、模型介绍 线性模型是在实践中广泛使用的一类模型&#xff0c;该模型利用输入特征的线性函数进行预测。 二、用于回归的线性模型 以下代码可以在一维wave数据集上学习参…

java内存模型介绍

Java内存模型&#xff08;Java Memory Model&#xff0c;JMM&#xff09;是一种规范&#xff0c;它定义了Java虚拟机&#xff08;JVM&#xff09;如何在内存中存储和访问Java对象的方式&#xff0c;以及多个线程如何访问这些对象时的规则。它的主要目标是定义程序中的各个线程如…

Python语言绘制好看的小提琴图、箱形图、散点图、山脊图和柱状图等等

废话不多说&#xff0c;今天给大家分享一个&#xff0c;使用python绘制小提琴图、箱形图、散点图、山脊图和柱状图等等 图中的数据是随机生成的&#xff0c;图例&#xff0c;图注以及坐标题目各种信息&#xff0c;具体内容大家可以自己修改~ 效果图如下所示 &#x1f447;&a…

ML307R OpenCPU 数据保存文件系统fs使用

一、函数介绍 二、实现数据保存 三、代码下载地址 一、函数介绍 以下是cm_fs.h里面的函数介绍 /*** brief 文件指针定位** param [in] fd 文件描述符* param [in] offset 指针偏移量* param [in] base 偏移起始点&#xff0c;CM_FS_SEEK_SET&#xff1a;文件开头 CM_FS…