进程、线程和进程间通信课程 Day4学习要点总结

本章是线程的取消 、清理,互斥和同步,以及互斥锁、读写锁和死锁的内容

一、线程取消机制

(一)核心概念

线程取消不是 “立刻杀死”,而是一种协作式机制

  • 发起方:主线程用 pthread_cancel(tid) 发送 “取消请求”
  • 接收方:子线程需遇到取消点(或主动调用 pthread_testcancel())才会响应
  • 结果:子线程响应后,会模拟执行 pthread_exit(PTHREAD_CANCELED),主线程 pthread_join 能拿到这个特殊返回值

(二)关键 API 分类详解(结合场景记)

函数 作用 核心细节
pthread_cancel(tid) 发取消请求 只是 “通知”,不保证立刻执行
pthread_testcancel() 手动取消点 主动检查是否有取消请求,有则响应
pthread_setcancelstate() 控制 “是否允许取消” 可临时禁用 / 启用取消响应
pthread_setcanceltype() 控制 “何时响应取消” 延迟响应(DEFERRED)或立即响应(ASYNCHRONOUS

(三)代码示例与调试

1. 基础取消流程(依赖系统调用作为取消点)
#include 
#include 
#include 

void *func(void *arg) {
    printf("Child thread start\n");
    while (1) {
        // sleep(5) 是系统调用,属于“取消点”
        sleep(5); 
    }
    // 永远执行不到这里
    pthread_exit("thread return");
}

int main() {
    pthread_t tid;
    void *retv;

    pthread_create(&tid, NULL, func, NULL);
    sleep(5);       // 等子线程进入循环
    pthread_cancel(tid);  // 发取消请求
    pthread_join(tid, &retv); 

    // 子线程被取消后,retv 会是 PTHREAD_CANCELED
    printf("Thread return: %s\n", (retv == PTHREAD_CANCELED) ? "PTHREAD_CANCELED" : (char*)retv);
    return 0;
}

运行逻辑

  • 子线程卡 sleep(5)(系统调用 = 取消点),收到 pthread_cancel 后会终止
  • 主线程 pthread_join 拿到 PTHREAD_CANCELED,输出类似:
    Thread return: PTHREAD_CANCELED
 2. 手动加取消点(无系统调用时用)
void *func(void *arg) {
    printf("Child thread start\n");
    while (1) {
        // 手动插入取消点,检查是否有取消请求
        pthread_testcancel(); 
        // 模拟耗时操作(无系统调用,原本不会响应取消)
        sleep(1); 
    }
    pthread_exit("thread return");
}

// main 函数逻辑和之前一样...

关键pthread_testcancel() 让 “非系统调用” 的循环也能响应取消请求

3. 控制取消使能(临时禁用取消)
void *func(void *arg) {
    printf("Child thread start\n");

    // 禁用取消响应:即使收到 pthread_cancel 也不处理
    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);  
    {
        // 这里即使有取消请求,也不会响应
        sleep(5); 
        // 手动检查(但因为禁用了,检查也没用)
        pthread_testcancel(); 
    }
    // 重新允许取消响应
    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);  

    while (1) {
        sleep(1);
    }
    pthread_exit("thread return");
}

效果

  • PTHREAD_CANCEL_DISABLE 区间内,pthread_cancel 发的请求会被 “暂时屏蔽”
  • 出了这个区间后,才会检查并响应之前的请求
4. 控制取消类型(立即 vs 延迟响应)
void *func(void *arg) {
    printf("Child thread start\n");

    // 设置“延迟响应”(默认就是这个,可显式设置)
    pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);  
    {
        // 遇到取消点(如 sleep)才会响应
        sleep(5); 
    }

    // 设置“立即响应”
    pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);  
    {
        // 无需等取消点,收到请求会立刻终止
        sleep(5); 
    }

    pthread_exit("thread return");
}

区别

  • DEFERRED(延迟):必须等 “取消点”(系统调用 / pthread_testcancel)才响应
  • ASYNCHRONOUS(立即):理论上随时可能终止(但实际受系统实现限制,极端场景才会明显)

二、线程清理机制

(一)必要性

  • 线程非正常终止时(如被取消),需要释放资源(如打开的文件、分配的内存)
  • pthread_exitpthread_cancel会触发清理函数,return不会

(二)核心函数

1. 注册清理函数
void pthread_cleanup_push(void (*routine)(void *), void *arg);
// routine:清理函数,arg:传递给清理函数的参数
2. 执行清理函数
void pthread_cleanup_pop(int execute);
// execute=1:执行清理函数;execute=0:不执行

(三)代码示例

#include 
#include 
#include 

void cleanup(void *arg) {
    printf("cleanup, arg=%s\n", (char *)arg);
}

void cleanup2(void *arg) {
    printf("cleanup2, arg=%s\n", (char *)arg);
}

void *func(void *arg) {
    printf("This is child thread\n");
    pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);//立即取消

    // 注册清理函数,成对出现
    pthread_cleanup_push(cleanup, "abcd");
    pthread_cleanup_push(cleanup2, "efgh");

    {
        sleep(1);
    }

    while (1) {
        printf("sleep\n");
        sleep(1);
    }

    // 实际因死循环到不了这里,只是演示成对写
    pthread_cleanup_pop(1); 
    pthread_cleanup_pop(1);
    pthread_exit("thread return");
}

int main() {
    pthread_t tid;
    void *retv;

    pthread_create(&tid, NULL, func, NULL);
    sleep(3);  // 等待线程运行起来
    pthread_cancel(tid);  // 发起取消,触发清理函数
    pthread_join(tid, &retv);
    while (1) {
        sleep(1);
       }
    return 0;

}
This is child thread
sleep
sleep
cleanup2, arg=efgh
cleanup, arg=abcd

(四)注意事项

  1. pthread_cleanup_pushpthread_cleanup_pop必须成对出现
  2. 清理函数执行顺序与注册顺序相反(后注册先执行)
  3. 线程通过return结束时,清理函数不会执行

三、线程的互斥与同步机制

(一)基本概念

1. 临界资源与临界区
  • 临界资源:一次仅允许一个线程访问的共享资源(如文件、打印机、全局变量)
  • 临界区:访问临界资源的代码段,需保证互斥访问
2. 互斥机制
  • 互斥锁(Mutex):最基本的同步原语,提供排他性访问
  • 读写锁(RWLock):允许多个读线程并发访问,写线程独占访问
  • 原子操作:针对简单变量的无锁同步(如 atomic 类型)

(二)互斥锁(Mutex)详解

1. 创建与销毁
// 静态初始化(适用于全局/静态变量)
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

// 动态初始化(运行时创建,需手动销毁)
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
2. 锁操作
int pthread_mutex_lock(pthread_mutex_t *mutex);   // 加锁(阻塞)
int pthread_mutex_trylock(pthread_mutex_t *mutex); // 尝试加锁(非阻塞)
int pthread_mutex_unlock(pthread_mutex_t *mutex); // 解锁
3. 示例分析(两个线程写文件)
//两个线程同时写一个文件
#include 
#include 
#include 
#include 
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//静态方式
FILE *fp;
void *func2(void *arg){
    pthread_detach(pthread_self());
    printf("This func2 thread\n");
    char str[]="I write func2 line\n";
    char c;
    int i=0;
    while(1){//一个字符一个字符的写
        pthread_mutex_lock(&mutex);
        while(i

四、读写锁机制

(一)特性

  • 写锁(排他锁):同一时间仅允许一个写者持有
  • 读锁(共享锁):同一时间允许多个读者持有
  • 优先级:写锁优先级高于读锁,避免写者饥饿

(二)读写锁操作

1. 初始化
pthread_rwlock_t rwlock;
pthread_rwlock_init(&rwlock, NULL);
2. 加锁与解锁
pthread_rwlock_rdlock(&rwlock);   // 读加锁(阻塞)
pthread_rwlock_tryrdlock(&rwlock); // 读加锁(非阻塞)
pthread_rwlock_wrlock(&rwlock);   // 写加锁(阻塞)
pthread_rwlock_trywrlock(&rwlock); // 写加锁(非阻塞)
pthread_rwlock_unlock(&rwlock);   // 解锁

(三)代码示例:读写混合场景

#include 
#include 
#include 
#include 
pthread_rwlock_t rwlock; //定义一个结构体 
FILE *fp;

void * read_func(void *arg){
    pthread_detach(pthread_self());
    printf("read thread\n");
    char buf[32]={0};
    while(1){
        pthread_rwlock_rdlock(&rwlock);//读锁
        while(fgets(buf,32,fp)!=NULL){
            printf("%d,rd=%s\n",(int)arg,buf);
            usleep(1000);
        }
        pthread_rwlock_unlock(&rwlock);
        sleep(1);
    }
}


void *func2(void *arg){
    pthread_detach(pthread_self());
    printf("This func2 thread\n");
    
    char str[]="I write func2 line\n";
    char c;
    int i=0;
    while(1){
        pthread_rwlock_wrlock(&rwlock);
        while(i

运行结果

read thread
read thread
This is func1 thread
This func2 thread
1,rd=I write func2 line
2,rd=I write func2 line
1,rd=You read func1 thread
2,rd=You read func1 thread
1,rd=I write func2 line
2,rd=I write func2 line
...

五、死锁问题

(一)概念

  • 死锁:多个线程互相等待对方持有的锁,导致所有线程阻塞
  • 必要条件:互斥、请求与保持、不剥夺、循环等待
示例代码
#include 
#include 
#include 
#include 
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
FILE *fp;
void *func2(void *arg){
    pthread_detach(pthread_self());
    printf("This func2 thread\n");
    
    char str[]="I write func2 line\n";
    char c;
    int i=0;
    while(1){
        pthread_mutex_lock(&mutex);
        printf("%d,I got lock2\n",(int)arg);
        sleep(1);
        pthread_mutex_lock(&mutex2);
        printf("%d,I got 2 locks\n",(int)arg);
        pthread_mutex_unlock(&mutex2);
        pthread_mutex_unlock(&mutex1);
        sleep(10);
    }
    pthread_exit("func2 exit");
}
void *func(void *arg){
    pthread_detach(pthread_self());
    printf("This is func1 thread\n");
    char str[]="You read func1 thread\n";
    char c;
    int i=0;
    while(1){
        pthread_mutex_lock(&mutex);
        printf("%d,I got lock1\n",(int)arg);
        sleep(1);
        pthread_mutex_lock(&mutex2);
        printf("%d,I got 2 locks\n",(int)arg);
        pthread_mutex_unlock(&mutex2);
        pthread_mutex_unlock(&mutex);
        sleep(10);
    }
    pthread_exit("func1 exit");
}
int main(){
    pthread_t tid,tid2;
    void *retv;
    int i;
    fp = fopen("1.txt","a+");
    if(fp==NULL){
        perror("fopen");
        return 0;
    }
pthread_create(&tid,NULL,func,1);
    pthread_create(&tid2,NULL,func2,2);
    while(1){    
        sleep(1);
    } 
}

 死锁触发流程

  • T0 时刻

    • func获取mutex,打印 "I got lock1"
    • func2获取mutex2,打印 "I got lock2"
  • T1 时刻

    • func尝试获取mutex2(被func2持有),进入阻塞
    • func2尝试获取mutex(被func持有),进入阻塞
  • 结果:两个线程永久等待对方释放锁,形成死锁

你可能感兴趣的:(学习,java,jvm)