文件IO函数(线程)

本文详细介绍了线程的概念,包括线程与进程的区别,线程的创建、退出以及线程间的同步互斥机制,如互斥锁的工作原理、使用方法,信号量和条件变量的原理及应用实例。文章还提供了多线程拷贝图片的两种实现方式,展示了如何在多线程环境中确保资源的安全访问。
摘要由CSDN通过智能技术生成

1.线程的概念

1.1什么是线程

  1. 线程:是一个进程并发执行多种任务的机制。
  2. 并发:单核cpu多个任务同时运行。(cpu以ms级别的速度进行进程调度,切换进程、线程)

串行、并发、并行

串行:多个任务有序执行,一个任务执行完毕后,再去执行另外一个任务

并发:多个任务在单核cpu上运行,同一个时间片上只能运行一个任务,cpu不停在各个任务上切换。

并行:多任务在多核cpu上运行,多个任务可以同时在不同的cpu核上执行。

  1. 进程的上下文切换

    • 上下文:运行一个进程所需要的所有资源。
    • 上下文切换:切换进程的时候,cpu访问的资源从访问A替换原有内容,到访问B。这个过程是一个耗时操作
    • 为了提高系统性能,引入了一个轻量级进程的概念,称之为线程。
  2. 线程运行在进程的空间内,线程是任务运行的最小单位!

  3. 每一个进程至少需要一个线程作为指令执行体。

  4. 每个进程中可以运行多个线程,称之为多线程。线程与线程执行根据cpu时间片进行调度

1680438804286

1.2 线程是任务运行的最小单位(重点!!)

属于同一个进程下的线程,共享其附属进程的所有资源。

共享:

  1. 静态存储区: .bss .data .rodata (全局变量, 静态变量)
  2. 堆区
  3. 栈区:

不共享:

  1. 线程tid号,线程调度块等等…

1.3 进程和线程的区别(重点!!!!)

  1. 进程是资源分配的最小单位,线程是任务运行的最小单位;
  2. 进程与进程之间的用户空间相互独立,内核空间共享。进程之间如果要做数据通信,需要引入进程间通信机制(IPC).
  3. 线程与线程之间共享其附属进程的所有资源。所以线程之间通信不需要通信机制,但是要注意同步互斥。
  4. 多线程的效率比多进程高。
  5. 多进程的稳定性比多线程高。
  6. 多进程的资源量比多线程多。

2.线程的函数

Compile and link with -pthread. 编译的时候需要手动连接线程库

如(vim编译时): gcc main.c -pthread

2.1 pthread_create(创建线程)

功能: 创建一个线程

原型:

#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);

参数:

  • pthread_t *thread:该指针指向的内存空间存储创建成功后的线程tid号;

  • pthread_attr_t attr:线程属性,一般填NULL,代表默认属性。
    如果要修改属性,需要用pthread_attr_init函数初始化线程属性,可以设置为分离属性;

  • void *(*tart_routine) (void *):回调函数,指定创建成功后的线程所要执行的任务;
    函数指针,可以指向返回值是void类型,参数列表是void类型的函数。ps:函数名就是函数首地址
    void* function(void* arg){ }

  • void *arg:传递给回调函数的参数;

返回值:

  • 成功,返回0;
  • 失败,返回error_number,没有说更新errno,所以无法用perror打印错误信息;

1680445910006

注意:

  1. 主线程退出后会导致进程退出,线程运行在进程空间内。进程结束,线程也会强制结束;
  2. 分支线程退出,不会影响主线程执行。且分支线程退出后不会运行到主线程的代码。
  3. 主线程与分支线程共享其附属进程的所有资源。

2.1.1主线程局部变量传参给分支线程

1680446078905

image-20230402225028235

2.1.2将分支线程的局部变量传参给主线程

1.方式一

image-20230402225051945

image-20230402225058327

2.方式二

image-20230402225113042

image-20230402225118234

2.2 pthread_exit(退出线程)

功能: 退出线程

原型:

#include <pthread.h>        
void pthread_exit(void *retval); 

参数:

  • void *retval:传递线程退出状态值,该状态值可以被 pthread_join 函数接收。如果不想传递则填NULL即可;

2.3 pthread_join(等待线程退出)

功能:阻塞函数,阻塞等待指定的线程退出,接收退出线程返回的值,并回收退出线程的资源。

原型:

#include <pthread.h>        
int pthread_join(pthread_t thread, void  **retval); 

**参数: **

  • pthread_t thread:指定要回收哪个线程,填对应的tid号;

  • void **retval:如果不为NULL,则会将 pthread_exit 传递的退出状态复制到该二级指针指向的一级指针中。为NULL,不接收线程退出状态值;

返回值:

  • 成功,返回0;
  • 失败,返回 error_number ,没有说更新errno,所以无法用perror打印错误信息;

image-20230402230231696

2.4 pthread_detach(分离线程)

功能:分离线程,线程退出后资源由内核自动回收

原型:

#include <pthread.h>        
int pthread_detach(pthread_t thread); 

**参数:**pthread_t thread:指定要分离的线程;

返回值:

  • 成功,返回0;
  • 失败,返回error_number,没有说更新 errno ,所以无法用 perror 打印错误信息;
pthread_t tid;
    if(pthread_create(&tid, NULL, callBack, NULL) != 0)
    {
        fprintf(stderr, "pthread_create failed __%d__\n", __LINE__);
        return -1;
    }
    pthread_detach(tid);    //分离线程,分离后不需要其他线程调用join接收   

注意:

当调用pthread_detach函数分离了分支线程A,此时其他线程调用pthread_join函数回收A线程将无效。pthread_join函数回收A,不会阻塞!!!

2.5 pthread_cancel(请求线程退出)

功能:请求指定线程退出; 请求成功不代表线程会 退出

原型:

#include <pthread.h>        
int pthread_cancel(pthread_t thread);

参数:

  • pthread_t thread:指定要请求的线程tid号:

返回值:

  • 成功,返回0;
  • 失败,返回error_number,没有说更新errno,所以无法用perror打印错误信息;

注:

1. pthread_cancel会给目标线程打上一个退出标识,cpu切换到目标线程后,运行到退出标识,才会让目标线程退出。

2. 但是while for 等循环结构,以及算数运算等等位置,无法打上退出标识。所以当目标线程只有上述结构的时候,无法用pthread_cancel退出线程

3. printf ,sleep等等函数结构可以打上退出标识

4. 请求成功,不代表目标线程一定会退出

3. 线程同步互斥机制

临界资源(共享资源):多个任务并发执行的时候,访问同一个资源,我们将这个资源称之为临界资源。

临界区:访问临界资源的代码,称之为临界区

线程之间,如果要进行通信,需要引入同步互斥机制,避免产生竞态。保证任何一个时刻,只有一个线程处理临界资源。

同步互斥机制:

  1. 互斥锁 (pthread_mutex_t)
  2. 条件变量 (pthread_cond_t)
  3. 信号量 (sem_t)

3.1 互斥锁

3.1.1工作原理

  1. 对于要访问临界资源的线程,在访问之前先申请互斥锁。

    1. 如果申请上锁成功,则进入临界区执行临界区代码,直到退出临界区,解开互斥锁。
    2. 如果申请上锁失败,则说明互斥锁被别的线程占用,线程进入休眠,等待互斥锁解开。
  2. 互斥锁只能保证临界区完整,只有一个线程访问,但是无法指定访问者的顺序。

3.1.2 pthread_mutex_init(创建并初始化互斥锁)

功能:创建并初始化互斥锁

原型:

#include <pthread.h>        
pthread_mutex_t fastmutex = PTHREAD_MUTEX_INITIALIZER;//用于创建初始化的宏       
int  pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr); //创建初始化的函数

参数:

  • pthread_mutex_t *mutex:该指针指向的内存空间,存储申请后的互斥锁
  • const pthread_mutexattr_t *mutexattr:互斥锁属性,该属性可以指定互斥锁是用于进程还是用于线程的同步互斥。 填NULL代表用于线程.。

返回值: 永远返回0(即,永远成功)

3.1.3 pthread_mutex_lock(上锁)

功能:对互斥锁进行上锁,若有其他线程占用互斥锁,该函数会阻塞

原型:

#include <pthread.h>       
int pthread_mutex_lock(pthread_mutex_t *mutex); 

参数:

  • pthread_mutex_t *mutex; 要上锁的指针

返回值:

  • 成功,返回0;

  • 失败,返回非0,没有说更新errno,所以不要用perror打印错误。

3.1.4 pthread_mutex_unlock(解锁)

功能:解开互斥锁

原型:

#include <pthread.h>        
int pthread_mutex_unlock(pthread_mutex_t *mutex); 

参数:

  • pthread_mutex_t *mutex; //待解锁的指针

返回值:

  • 成功,返回0;
  • 失败,返回非0,没有说更新errno,所以不要用perror打印错误。

3.1.5 pthread_mutex_destroy(解锁)

功能:销毁互斥锁

原型:

#include <pthread.h>       
int pthread_mutex_destroy(pthread_mutex_t *mutex)

参数:

  • pthread_mutex_t *mutex;

返回值:

  • 成功,返回0;

  • 失败,返回非0,没有说更新errno,所以不要用perror打印错误。

3.1.6 死锁

拥有锁资源的任务没有释放锁

  1. 持有互斥锁的线程异常退出,没有释放锁资源。
  2. 同一线程对一把互斥锁重复上锁。
  3. 互斥锁交叉嵌套。

注:写代码的时候要注意,互斥锁是否解开

3.1.7 互斥锁的示例代码

#include <stdio.h>
#include <pthread.h>
#include <string.h>
//临界资源
char buf[] = "1234567";
//互斥锁,采用宏的方式定义"锁"
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void* callBack_print(void* arg)//循环打印函数
{
    while(1)
    {
        /***********临界区***********/
        pthread_mutex_lock(&mutex);     //上锁
        printf("%s\n", buf);
        pthread_mutex_unlock(&mutex);   //解锁
        /***********临界区***********/
    }
    pthread_exit(NULL);
}

void* callBack_swap(void* arg)//循环逆置函数
{/**** 采用首尾地址的方式****/
    char* start, *end;
    char temp;
    while(1)
    {
        /***********临界区***********/
        pthread_mutex_lock(&mutex);     //上锁


        start = buf;
        end = buf+strlen(buf)-1;
        while(start < end)
        {
            temp = *start;
            *start = *end;
            *end = temp;
            start++;
            end--;
        }

        pthread_mutex_unlock(&mutex);   //解锁
        /***********临界区***********/
    }
    pthread_exit(NULL);
}

int main(int argc, const char *argv[])
{
    //创建互斥锁  方式一                                                                      
    //pthread_mutex_init(&mutex, NULL);

    pthread_t tid_p, tid_s;
    if(pthread_create(&tid_p, NULL, callBack_print, (void*)&mutex) != 0)
    {
        fprintf(stderr, "pthread_create failed __%d__\n", __LINE__);
        return -1;
    }
    pthread_detach(tid_p);  //分离线程

    if(pthread_create(&tid_s, NULL, callBack_swap, NULL) != 0)
    {
        fprintf(stderr, "pthread_create failed __%d__\n", __LINE__);
        return -1;
    }


    pthread_join(tid_s, NULL);  //阻塞等待tid_s线程退出
    pthread_mutex_destroy(&mutex); //销毁互斥锁

    return 0;
}

练习:要求用两个线程拷贝一张图片。

A线程拷贝前半部分,B线程拷贝后半部分,不允许使用sleep函数

方式一:开一次资源,用互斥锁记录偏移量
#include <stdio.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

//需要传入到分支线程的资源
struct filemsg
{
    int fd_r;
    int fd_w;
    off_t size;
};

//互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;


void* callBack1(void* arg)      //void* arg = &info
{
    int fd_r = ((struct filemsg*)arg)->fd_r;
    int fd_w = ((struct filemsg*)arg)->fd_w;
    off_t size = ((struct filemsg*)arg)->size;


    //将偏移量修改到文件开头
    off_t offset = 0;

    //循环拷贝前半部分
    char c = 0;
    for(int i=0; i<size/2; i++)
    {   
        /***********临界区***************/
        pthread_mutex_lock(&mutex);     //上锁

        lseek(fd_r, offset, SEEK_SET);  
        lseek(fd_w, offset, SEEK_SET);  

        if(read(fd_r, &c, 1) <=0 )
        {
            perror("read");
            return NULL;
        }
        if(write(fd_w, &c , 1) < 0)
        {
            perror("write");
            return NULL;
        }
        offset = lseek(fd_r, 0, SEEK_CUR);

        pthread_mutex_unlock(&mutex);   //解锁
        /***********临界区***************/
    }   
    printf("前半部分拷贝完毕\n");


    pthread_exit(NULL);
}

void* callBack2(void* arg)
{
    int fd_r = ((struct filemsg*)arg)->fd_r;
    int fd_w = ((struct filemsg*)arg)->fd_w;
    off_t size = ((struct filemsg*)arg)->size;



    //将偏移量修改到文件size/2
    off_t offset = size/2;

    //循环拷贝前半部分
    char c = 0;
    for(int i=size/2; i<size; i++)
    {   
        /***********临界区***************/
        pthread_mutex_lock(&mutex);     //上锁
        lseek(fd_r, offset, SEEK_SET);  
        lseek(fd_w, offset, SEEK_SET);  

        if(read(fd_r, &c, 1) <=0 )
        {
            perror("read");
            return NULL;
        }
        if(write(fd_w, &c , 1) < 0)
        {
            perror("write");
            return NULL;
        }
        offset = lseek(fd_r, 0, SEEK_CUR);

        pthread_mutex_unlock(&mutex);   //解锁
        /***********临界区***************/

    }   
    printf("后半部分拷贝完毕\n");


    pthread_exit(NULL);
}



int main(int argc, const char *argv[])
{
    int fd_r = open("./1.png", O_RDONLY);
    if(fd_r < 0)
    {   
        perror("open");
        pthread_exit(NULL);
    }   


    int fd_w = open("./2.png", O_WRONLY|O_CREAT|O_TRUNC, 0664);
    if(fd_w < 0)
    {   
        perror("open");
        return -1; 
    }   

    //计算文件大小
    off_t size = lseek(fd_r, 0, SEEK_END);

    struct filemsg info;
    info.fd_r = fd_r;
    info.fd_w = fd_w;
    info.size = size;


    pthread_t tid1, tid2;
    if(pthread_create(&tid1, NULL, callBack1, &info) != 0)
    {   
        fprintf(stderr, "pthread_create failed __%d__\n", __LINE__);
        return -1; 
    }   
    if(pthread_create(&tid2, NULL, callBack2, &info) != 0)
    {   
        fprintf(stderr, "pthread_create failed __%d__\n", __LINE__);
        return -1; 
    }                                                                               

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    close(fd_r);
    close(fd_w);

    //销毁互斥锁
    pthread_mutex_destroy(&mutex);
    return 0;
}
方式二:开两份资源,线程各自运行各自的
#include <stdio.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

void* callBack1(void* arg)
{
    int fd_r = open("./1.png", O_RDONLY);
    if(fd_r < 0)
    {
        perror("open");
        pthread_exit(NULL);
    }

    int fd_w = open("./2.png", O_WRONLY);                                 
    if(fd_w < 0)
    {
        perror("open");
        close(fd_r);
        pthread_exit(NULL);
    }


    //计算文件大小
    off_t size = lseek(fd_r, 0, SEEK_END);

    //将偏移量修改到文件开头
    lseek(fd_r, 0, SEEK_SET);
    lseek(fd_w, 0, SEEK_SET);

    //循环拷贝前半部分
    char c = 0;
    for(int i=0; i<size/2; i++)
    {
        read(fd_r, &c, 1);
        write(fd_w, &c , 1);
    }
    printf("前半部分拷贝完毕\n");

    close(fd_r);
    close(fd_w);
    pthread_exit(NULL);
}

void* callBack2(void* arg)
{
    int fd_r = open("./1.png", O_RDONLY);
    if(fd_r < 0)
    {
        perror("open");
        pthread_exit(NULL);
    }

    int fd_w = open("./2.png", O_WRONLY);
    if(fd_w < 0)
    {
        perror("open");
        close(fd_r);
        pthread_exit(NULL);
    }
    //计算文件大小
    off_t size = lseek(fd_r, 0, SEEK_END);

    //将偏移量修改到文件size/2
    lseek(fd_r, size/2, SEEK_SET);
    lseek(fd_w, size/2, SEEK_SET);

    //循环拷贝前半部分
    char c = 0;
    for(int i=size/2; i<size; i++)
    {
        read(fd_r, &c, 1);
        write(fd_w, &c , 1);
    }
    printf("后半部分拷贝完毕\n");



    close(fd_r);
    close(fd_w);
    pthread_exit(NULL);
}



int main(int argc, const char *argv[])
{
    int fd_w = open("./2.png", O_WRONLY|O_CREAT|O_TRUNC, 0664);
    if(fd_w < 0)
    {
        perror("open");
        return -1;
    }
    close(fd_w);

    pthread_t tid1, tid2;
    if(pthread_create(&tid1, NULL, callBack1, NULL) != 0)
    {
        fprintf(stderr, "pthread_create failed __%d__\n", __LINE__);
        return -1;
    }
    if(pthread_create(&tid2, NULL, callBack2, NULL) != 0)
    {
        fprintf(stderr, "pthread_create failed __%d__\n", __LINE__);
        return -1;
    }

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    return 0;
}

3.2 信号量(又称为:信号灯)

3.2.1 工作原理

  1. 对于要访问临界资源的线程,都去执行申请信号量的操作。

    • 当信号量的值大于0,则申请成功,信号量的值-1;
    • 当信号量的值等于0,则申请失败,该申请操作会阻塞,线程进入休眠,等待信号量的值大于0;
  2. 互斥锁又称之为二值信号量,只允许一个线程进入临界区,即信号量的初始值为1;

  3. 信号量根据初始值不同,可以让一个或者多个线程同时进入临界区。例如初始值为2,则允许两个线程同时进入临界区。

  4. PV操作:实现线程、进程同步互斥的有效方式

    • P操作:申请信号量,减操作 -1
    • V操作:释放信号量,加操作 +1

3.2.2 sem_init(创建并初始化信号量)

功能:创建并初始化信号灯

原型:

#include <semaphore.h>        
int sem_init(sem_t *sem, int pshared, unsigned int value); 

参数:

  • sem_t *sem:该指针指向的内存空间中会存储创建好的信号灯;

  • int pshared:共享标识,用于指定是用在线程之间还是进程之间;

    • 0:用于线程

    • 非0: 用于进程;

  • unsigned int value:指定信号灯的初始值;

返回值:

  • 成功,返回0;
  • 失败,返回-1,更新errno;

3.2.4 sem_wait(P操作,申请信号量)

功能:对信号量做P操作 -1操作(申请信号量)

  • 当信号量中的值大于0,则申请信号量成功,线程进入临界区。
  • 当信号量中的值等于0,则申请信号量失败,线程进入休眠阻塞阶段,直到信号量的值大于0,解除阻塞。

原型:

#include <semaphore.h>        
int sem_wait(sem_t *sem); 

返回值:

  • 成功,返回0;
  • 失败,返回-1,更新errno;

3.2.5 sem_post(V操作,释放信号量)

功能:对指定信号量做V操作;(释放信号量)

原型:

#include <semaphore.h>       
int sem_post(sem_t *sem); 

返回值:

  • 成功,返回0;
  • 失败,返回-1,更新errno;

3.2.6 sem_destroy(销毁信号量)

功能:销毁信号量

原型:

#include <semaphore.h>        
int sem_destroy(sem_t *sem); 

参数:

  • sem_t *sem: 信号量指针

返回值:

  • 成功,返回0;
  • 失败,返回-1,更新errno;

3.2.7 信号量示例代码

#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <semaphore.h>

//临界资源
char buf[] = "AAAA____BBBB";

//信号量
sem_t sem;

void* callback1(void* arg)
{
    while(1)
    {
        /******临界区************/
        //P操作
        if(sem_wait(&sem) < 0)
        {
            perror("sem_wait");                                                                             
            break;
        }

        printf("%s\n", buf);

        //V操作 
        if(sem_post(&sem) < 0)
        {
            perror("sem_post");
            break;
        }

        /******临界区************/
    }
    pthread_exit(NULL);
}


void* callback2(void* arg)
{
    char temp = 0;

    while(1)
    {
        /******临界区************/
        //P操作
        if(sem_wait(&sem) < 0)
        {
            perror("sem_wait");
            break;
        }

        for(int i=0; i<strlen(buf)/2; i++)
        {
            temp = buf[i];
            buf[i] = buf[strlen(buf)-1-i];
            buf[strlen(buf)-1-i] = temp;
        }

        //V操作 
        if(sem_post(&sem) < 0)
        {
            perror("sem_post");
            break;
        }

        /******临界区************/
    }


    pthread_exit(NULL);
}


int main(int argc, const char *argv[])
{
    //创建信号量
    if(sem_init(&sem, 0, 1) < 0)
    {
        perror("sem_init");
        return -1;
    }

    pthread_t tid1, tid2;
    //创建线程:打印
    if(pthread_create(&tid1, NULL, callback1, NULL) != 0)
    {
        fprintf(stderr, "pthread_create failed\n");
        return -1;
    }
    pthread_detach(tid1);

    //创建线程:倒置
    if(pthread_create(&tid2, NULL, callback2, NULL) != 0)
    {
        fprintf(stderr, "pthread_create failed\n");
        return -1;
    }


    pthread_join(tid2, NULL);

    //销毁信号量
    sem_destroy(&sem);

    return 0;
}

3.3 条件变量

3.3.1 工作原理

  1. 将不访问共享资源的线程直接休眠,并设置一个唤醒条件,该唤醒条件称之为条件变量;
  2. 当到线程需要访问的时候,其他线程通过指定的条件变量唤醒该线程。

3.3.2 pthread_cond_init(创建并初始化信号量)

功能:创建并初始化条件变量;

原型:

#include <pthread.h>       
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;       
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr); 

参数:

  • pthread_cond_t *cond:该指针指向的内存空间中,存储申请后的条件变量;
  • const pthread_condattr_t *condattr:条件变量属性,该属性可以指定条件变量是用于进程还是用于线程的同步互斥。 填NULL代表用于线程.。

返回值:

  • 成功,返回0;
  • 失败,返回非0;

3.3.3 pthread_cond_destroy(销毁条件变量)

功能: 销毁条件变量

原型:

#include <pthread.h>
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int pthread_cond_destroy(pthread_cond_t *cond);

参数:

  • pthread_cond_t *cond

返回值:

3.3.4 pthread_cond_wait(设置一个唤醒条件, 解开互斥锁,休眠等待被唤醒)

功能:设置一个唤醒条件, 解开互斥锁,休眠等待被唤醒

  • 当其他线程用pthread_cond_signal唤醒后,该函数尝试上锁
  • 如果上锁成功,则唤醒成功 ,当成功被唤醒后,会从当前位置继续往后执行
  • 如果上锁失败,则重新回到cond上继续休眠

原型:

#include <pthread.h>       
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); 

参数:

  • pthread_cond_t *cond:指定唤醒条件,即指定条件变量; 一般称线程睡在该条件变量上;

  • pthread_mutex_t *mutex:指定要解开的互斥锁;

返回值:

  • 成功,返回0;

  • 失败,返回非0;

注意:

  1. pthread_cond_wait会将线程放入cond_wait队列,解开互斥锁,等待被唤醒。(原子操作:即cpu资源不会被切走的操作)

  2. pthread_cond_signal函数,会将线程从cond_wait队列切换到mutex_wait队列中。同时是signal计数器+1;

  3. 线程会在mutex_wait队列中等待锁资源。

  4. 当成功获取到锁资源后,线程会成功被唤醒,此时signal计数器-1;

  5. 若mutex_wait队列中有多个线程,则没有获取到锁资源的线程会重新回到cond_wait队列上继续休眠,等待下一次唤醒。

    ps:如果上述不能理解,则简化版:pthread_cond_signal肯定会随机唤醒一个睡在cond上的线程。

image-20230404125734804

条件变量与互斥锁共用的原因

  1. pthread_cond_init创建的条件变量本质上是在内核中创建一个cond_wait 队列,凡是用pthread_cond_wait函数睡在cond上的条件变量,均会被添加到cond_wait队列中。
  2. 则有可能会出现,线程A调用pthread_cond_wait函数的时候,还没有运行到添加到队列中时,时间片结束。线程B调用pthread_cond_signal函数,导致漏唤醒。
  3. 所以加上互斥锁机制,在pthread_cond_wait中传入互斥锁,当pthread_cond_wait函数将线程A添加到队列后,再解开互斥锁。允许去执行pthread_cond_signal函数。

3.3.5 pthread_cond_signal(随机唤醒线程)

功能:通过指定条件变量,随机唤醒一个睡在指定条件变量上的线程;

原型:

#include <pthread.h>       
int pthread_cond_signal(pthread_cond_t *cond); 

参数:

  • pthread_cond_t *cond:唤醒指定条件变量上的线程;

返回值:

  • 成功,返回0;
  • 失败,返回非0;

3.3.6 pthread_cond_broadcast(唤醒所有线程)

功能: 唤醒所有睡在cond上的线程

原型:

int pthread_cond_broadcast(pthread_cond_t *cond);

参数:

  • pthread_cond_t *cond:唤醒指定条件变量上的线程;

3.3.7 条件变量示例代码

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

//临界资源
char buf[] = "1234567";
//互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

int flag = 0;   //0打印 1倒置

void* callBack_print(void* arg)
{
    while(1)
    {
        /***********临界区***********/
        pthread_mutex_lock(&mutex);     //上锁

        //当不是当前线程要访问的时机,则让当前线程休眠
        if(0 != flag)
        {
            //设置条件变量(唤醒条件),同时解开互斥锁
            //休眠,等待被唤醒
            pthread_cond_wait(&cond, &mutex);
            //尝试上锁,如果上锁成功,则唤醒成功
            //当成功被唤醒后,会从当前位置继续往后执行
            //如果上锁失败,则重新回到cond上继续休眠

        }

        printf("%s\n", buf);

        flag = 1;       //修改运行时机,设置为非0

        //通过指定条件变量唤醒指定线程
        pthread_cond_signal(&cond);

        pthread_mutex_unlock(&mutex);   //解锁
        /***********临界区***********/
    }
    pthread_exit(NULL);
}

void* callBack_swap(void* arg)
{
    char* start, *end;
    char temp;
    while(1)
    {
        /***********临界区***********/
        pthread_mutex_lock(&mutex);     //上锁

        //当不是当前线程要访问的时机,则让当前线程休眠
        if(1 != flag)
        {
            //设置条件变量(唤醒条件),同时解开互斥锁
            //休眠,等待被唤醒
            pthread_cond_wait(&cond, &mutex);
            //尝试上锁,如果上锁成功,则唤醒成功
            //当成功被唤醒后,会从当前位置继续往后执行
            //如果上锁失败,则重新回到cond上继续休眠
        }
        start = buf;
        end = buf+strlen(buf)-1;
        while(start < end)
        {
            temp = *start;
            *start = *end;
            *end = temp;
            start++;
            end--;
        }
        flag = 0;

        //通过指定条件变量唤醒指定线程
        pthread_cond_signal(&cond);


        pthread_mutex_unlock(&mutex);   //解锁
        /***********临界区***********/
    }
    pthread_exit(NULL);
}

int main(int argc, const char *argv[])
{
    //创建互斥锁  方式一
    //pthread_mutex_init(&mutex, NULL);

    //创建条件变量 方式一                                                                                               
    /*
    if(pthread_cond_init(&cond, NULL) != 0)
    {
        fprintf(stderr, "pthread_cond_init failed __%d__\n", __LINE__);
        return -1;
    }
    */

    pthread_t tid_p, tid_s;
    if(pthread_create(&tid_p, NULL, callBack_print, NULL) != 0)
    {
        fprintf(stderr, "pthread_create failed __%d__\n", __LINE__);
        return -1;
    }
    pthread_detach(tid_p);  //分离线程

    if(pthread_create(&tid_s, NULL, callBack_swap, NULL) != 0)
    {
        fprintf(stderr, "pthread_create failed __%d__\n", __LINE__);
        return -1;
    }


    pthread_join(tid_s, NULL);  //阻塞等待tid_s线程退出
    pthread_mutex_destroy(&mutex);  //销毁互斥锁
    pthread_cond_destroy(&cond);    //销毁条件变量

    return 0;
}
 }
    flag = 0;

    //通过指定条件变量唤醒指定线程
    pthread_cond_signal(&cond);


    pthread_mutex_unlock(&mutex);   //解锁
    /***********临界区***********/
}
pthread_exit(NULL);

}

int main(int argc, const char *argv[])
{
//创建互斥锁 方式一
//pthread_mutex_init(&mutex, NULL);

//创建条件变量 方式一                                                                                               
/*
if(pthread_cond_init(&cond, NULL) != 0)
{
    fprintf(stderr, "pthread_cond_init failed __%d__\n", __LINE__);
    return -1;
}
*/

pthread_t tid_p, tid_s;
if(pthread_create(&tid_p, NULL, callBack_print, NULL) != 0)
{
    fprintf(stderr, "pthread_create failed __%d__\n", __LINE__);
    return -1;
}
pthread_detach(tid_p);  //分离线程

if(pthread_create(&tid_s, NULL, callBack_swap, NULL) != 0)
{
    fprintf(stderr, "pthread_create failed __%d__\n", __LINE__);
    return -1;
}


pthread_join(tid_s, NULL);  //阻塞等待tid_s线程退出
pthread_mutex_destroy(&mutex);  //销毁互斥锁
pthread_cond_destroy(&cond);    //销毁条件变量

return 0;

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值