一、线程基础
进程是操作系统经典的执行任务的生产力。
进程是最小的资源分配单位,进程的内存开销较大,在内存资源不变的情况下,提高进程的执行能力(生产力)
线程寄存在进程中,与进程共享资源(内存),而后完成特定任务。
相比于传统的多进程并发程序,多线程开销更小,资源耗费更少,更加轻量级
1.1 线程如何分配系统资源
每一个进程都有一个内核对象(key),调度器根据key来分发时间片资源
用户级线程,是安装在用户层的。一些用户会在不支持线程机制的系统上安装用户线程,但是这类线程无法被系统识别和分配资源(时间片按进程数量分配)
多进程目的:得到更多时间片
主控线程(进程本身)是内核级线程,系统可分配内核对象,被CPU主动分发资源。内核线程可以实现物理级别的并行
sleep(0); 释放资源
用户线程不会被CPU分配时间片,但是可以使用主线程放弃的时间片(就近原则)。当多线程模型的某个线程陷入阻塞或挂起,其他线程可以接替资源继续执行,提高程序的执行速度,提高CPU的使用效率
多线程目的:更高的CPU访问频率
内核级线程的调度都有系统的参与,系统开销较大(上下文调度等)
用户级线程安装在用户空间,访问资源在用户层即可完成,无需内核干预。一般用户级线程都是以library第三方库的形式安装与使用的
AB型线程:为每个线程创建内核对象,可以被系统主动分配时间片,但是将线程安装在用户空间,大量的访问在用户空间即可完成。(只能在某些特定的系统上才支持)
1.2 进程的蜕变,如何区分进程和线程
进程是独占系统资源的
多线程:多个新的执行单元(普通线程)与原有执行单元(主控线程)共享存储空间
进程的蜕变:进程中自带的执行单元称为主控线程(main thread),其他称为普通线程(thread)
进程是最小的资源分配单位,线程是最小的调度单位,线程不额外分配内存资源,使用进程的
线程就是寄存器和栈:即可以使用cpu完成逻辑和运算,又可以保存恢复处理器现场,是系统中合格的调度单位。
-
线程可以使用CPU,可以遵循分时复用原则使用时间片
-
线程拥有独立的内核栈指针,可以保存自己的寄存器数据,可以保存恢复处理器现场。
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 互斥
多线程执行时,如果访问共享数据,可能会导致冲突和异常,开发者需要对其进行控制(互斥操作)
多线程访问同一个东西:
- IO访问:1.磁盘文件:如果多个线程对相同文件进行处理、引发文件数据异常
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;
}
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(); //唤醒所有线程
条件变量的数量:由工作条件数量决定,不同条件挂起的线程挂起在不同条件变量中,避免错误的唤醒