科林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 线程命令

查看线程

bash 复制代码
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库函数

cs 复制代码
#include <pthread.h>

gcc pthread_create.c -lpthread -o app

2.2.1 线程创建pthred_create()

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

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

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

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

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

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

cs 复制代码
#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()

cs 复制代码
pthread_t tid=pthear_self();    //返回当前线程tid

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

2.2.3 回收线程pthread_join()

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

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

cs 复制代码
pthread_join(pthread_tid,void** reval);  
cs 复制代码
#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()

cs 复制代码
pthread_cancel(pthread_t tid);    //杀死线程,取消线程

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

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

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

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

cs 复制代码
pthread_testcancel();    //只会触发系统空调用,不会进行额外任务
cs 复制代码
#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),这种线程结束后,系统自行回收线程资源,无需用户参与

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

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

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

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

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

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

cs 复制代码
#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),杀死整个进程

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

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

2.2.7 多线程开发模型

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

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

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

多线程模型:开销小。

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

2.4 线程属性

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

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

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

2.4.1 直接创建分离态线程

cs 复制代码
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)销毁线程属性

cs 复制代码
#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)

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

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

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

cs 复制代码
#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. 多线程同时处理相同的全局数据,引发异常:多线程访问全局变量,计算产生无效的重叠。如果后续线程无法在前置线程的结果上累加,导致结果异常
cs 复制代码
#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 互斥锁技术

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

cs 复制代码
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;    //互斥锁静态初始化

使用互斥锁保护全局资源

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

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

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

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

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

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

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

cs 复制代码
#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 读写锁

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

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

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

cs 复制代码
pthread_rwlock_t lock = PTHREAD_RWLOCK_INITIALIZER;    //读写锁静态初始化

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

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

写锁被占用,其他线程申请会阻塞等待

读锁有数量限制,如果读锁被耗尽,新线程挂起等待,直到被释放

cs 复制代码
#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)定义互斥锁属性

cs 复制代码
pthread_mutexattr_t attr;

(3)初始化互斥锁属性

cs 复制代码
pthread_mutexattr_init(&attr);

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

cs 复制代码
pthread_mutexattr_setpshared(&attr,
                            PTHREAD_PROCESS_SHARED/*进程锁,PTHREAD_PROCESS_PRIVATE线程锁*/);

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

cs 复制代码
pthread_mutex_init(&lock,&attr);
cs 复制代码
#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 文件锁

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

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

cpp 复制代码
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)替换文件原有的文件锁结构体

cs 复制代码
fcntl(fd,F_GETLK,struct flock* oldlock);    //获取某个文件的锁属性
fcntl(fd,F_SETLK,struct flock* newlock);    //非阻塞上锁,阻塞:F_SETLKW
cs 复制代码
//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;
}
cs 复制代码
//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 死锁问题

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

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

死锁问题:线程永久挂起

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

cs 复制代码
#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 线程同步

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

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

有效的线程控制手段

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

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

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

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

相关推荐
Bosenya122 分钟前
【信号处理】绘制IQ信号时域图、星座图、功率谱
开发语言·python·信号处理
monkey_meng3 分钟前
【Rust Crate之Actix Web(一)】
开发语言·后端·rust
AI原吾20 分钟前
探索PyAV:Python中的多媒体处理利器
开发语言·python·ai·pyav
oliveira-time30 分钟前
爬虫学习8
开发语言·javascript·爬虫·python·算法
星叔40 分钟前
ARXML汽车可扩展标记性语言规范讲解
java·前端·汽车
2401_8576009544 分钟前
SpringBoot框架:共享汽车管理的创新工具
java·spring boot·汽车
矛取矛求1 小时前
string接口的深度理解(内附思维导图)
c语言·开发语言·c++·接口·string
代码小鑫1 小时前
A15基于Spring Boot的宠物爱心组织管理系统的设计与实现
java·开发语言·spring boot·后端·毕业设计·宠物
爱吃土豆的马铃薯ㅤㅤㅤㅤㅤㅤㅤㅤㅤ1 小时前
mapper.xml 使用大于号、小于号示例
xml·java·数据库
点云侠1 小时前
二维椭圆拟合算法及推导过程
开发语言·c++·算法·计算机视觉·matlab