day24

一  使用临界资源完成多个线程之间的通信

#include<myhead.h>
char buf[128] = "";           //定义全局变量
 
//定义线程体1
void *task1(void *arg)
{
    while(1)
    {
        //从终端获取数据
 
        printf("请输入>>>");
        fgets(buf, sizeof(buf), stdin);
        buf[strlen(buf)-1] = 0;
    }
 
}
 
//定义线程体函数2
void *task2(void *arg)
{
    while(1)
    {
        usleep(500000);       //0.5秒执行一次
        printf("buf = %s\n", buf);
        bzero(buf, sizeof(buf));
    }
}
 
 
 
/******************主程序**********************/
int main(int argc, const char *argv[])
{
    //创建两个任务
    pthread_t tid1, tid2;
 
    if(pthread_create(&tid1, NULL, task1, NULL)!=0)
    {
        printf("task1 创建失败\n");
        return -1;
    }
    if(pthread_create(&tid2, NULL, task2, NULL)!=0)
    {
        printf("task2 创建失败\n");
        return -1;
    }
 
    //主线程
    printf("tid1 = %#lx, tid2 = %#lx\n", tid1, tid2);
 
    //阻塞回收线程资源
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    return 0;
}
 


二、线程的同步互斥机制


2.1    引入案例

#include<myhead.h>
 
int num = 520;        //票的个数
 
//定义线程体1
void *task1(void *arg)
{
    while(1)
    {
        if(num > 0)
        {
            usleep(1000);
            printf("张三买了一张票,剩余:%d\n", num);
            num--;
        }else
        {
            printf("票已经售完,购票失败\n");
            break;
        }
    
    }
 
    pthread_exit(NULL);         //退出线程
 
}
 
//定义线程体函数2
void *task2(void *arg)
{
    while(1)
    {
        if(num > 0)
        {
            usleep(1000);
            printf("李四买了一张票,剩余:%d\n", num);
            num--;
        }else
        {
            printf("票已经售完,购票失败\n");
            break;
        }
    
    }
 
    pthread_exit(NULL);         //退出线程
}
 
 
//定义线程体函数3
void *task3(void *arg)
{
    while(1)
    {
        if(num > 0)
        {
            usleep(1000);
            printf("王五买了一张票,剩余:%d\n", num);
            num--;
        }else
        {
            printf("票已经售完,购票失败\n");
            break;
        }
    
    }
 
    pthread_exit(NULL);         //退出线程
}
 
/******************主程序**********************/
int main(int argc, const char *argv[])
{
    //创建两个任务
    pthread_t tid1, tid2, tid3;
 
    if(pthread_create(&tid1, NULL, task1, NULL)!=0)
    {
        printf("task1 创建失败\n");
        return -1;
    }
    if(pthread_create(&tid2, NULL, task2, NULL)!=0)
    {
        printf("task2 创建失败\n");
        return -1;
    }
    if(pthread_create(&tid3, NULL, task3, NULL)!=0)
    {
        printf("task3 创建失败\n");
        return -1;
    }
    //主线程
    printf("tid1 = %#lx, tid2 = %#lx\n", tid1, tid2);
 
    //阻塞回收线程资源
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    pthread_join(tid3, NULL);
    return 0;
}
 
效果图:

 
解析:如上例所示,当其中一个线程正在访问全局变量时,可能会出现时间片用完的情况,另一个线程将数据进行更改后,再执行该线程时,导致数据的不一致。这种现象我们称为竞态,为了解决竞态,我们引入了同步互斥机制
 


2.2    基本概念


1>    竞态:同一个进程的多个线程在访问临界资源时,会出现抢占的现象,导致线程中的数据错误的现象称为竞态
2>    临界资源:多个线程共同访问的数据称为临界资源,可以是全局变量、堆区数据、外部文件
3>    临界区:访问临界资源的代码段称为临界区
4>    粒度:临界区的大小
5>    同步:表示多个任务有先后顺序的执行
6>    互斥:表示在访问临界区时,同一时刻只能有一个任务,当有任务在访问临界区时,其他任务只能等待
 


2.3    线程互斥


1>    在C语言中,线程的互斥通过互斥锁来完成
2>    互斥锁的本质:互斥锁本质上也是一个特殊的临界资源,该临界资源在同一时刻只能被一个线程所拥有,当一个线程试图去锁定被另一个线程锁定的互斥锁时,该线程会阻塞等待,直到拥有互斥锁的线程解锁了该互斥锁
3>    互斥锁的相关API

       #include <pthread.h>
       1、创建互斥锁:只需要定义一个pthread_mutex_t类型的变量,就创建了一个互斥锁
 
       pthread_mutex_t fastmutex = PTHREAD_MUTEX_INITIALIZER;           //静态初始化一个互斥锁
 
       2、初始化互斥锁
 
       int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);
       功能:初始化一个互斥锁
       参数1:互斥锁变量的地址
       参数2:互斥锁的属性,一般填NULL,使用系统默认提供的属性
       返回值:总是成功,不会失败
        
       3、获取锁资源
 
       int pthread_mutex_lock(pthread_mutex_t *mutex);
       功能:某个线程调用该函数表示获取锁资源
       参数:互斥锁的地址
       返回值:成功返回0,失败返回一个错误码
       
       4、释放锁资源
 
       int pthread_mutex_unlock(pthread_mutex_t *mutex);
       功能:将线程中的锁资源释放
       参数:互斥锁的地址
       返回值:成功返回0,失败返回一个错误码
       
        5、销毁锁资源
       int pthread_mutex_destroy(pthread_mutex_t *mutex);
       功能:销毁程序中的锁资源
       参数:互斥锁地址
       返回值:成功返回0,失败返回一个错误码
 

#include <myhead.h>
 
// 1、创建一个互斥锁
pthread_mutex_t mutex;
 
int num = 520; // 票的个数
 
// 定义线程体1
void *task1(void *arg)
{
        while (1)
        {
                // 3、获取锁资源
                pthread_mutex_lock(&mutex);
                if (num > 0)
                {
 
                        usleep(1000);
                        num--;
                        printf("张三买了一张票,剩余:%d\n", num);
 
                }
                else
                {
                        printf("票已经售完,购票失败\n");
                        // 4、释放锁资源
                        pthread_mutex_unlock(&mutex);
                        break;
                }
                // 4、释放锁资源
                pthread_mutex_unlock(&mutex);
        }
 
        pthread_exit(NULL); // 退出线程
}
 
// 定义线程体函数2
void *task2(void *arg)
{
        while (1)
        {
                // 3、获取锁资源
                pthread_mutex_lock(&mutex);
                if (num > 0)
                {
 
                        usleep(1000);
                        num--;
                        printf("李四买了一张票,剩余:%d\n", num);
 
                }
                else
                {
                        printf("票已经售完,购票失败\n");
                        // 4、释放锁资源
                        pthread_mutex_unlock(&mutex);
                        break;
                }
                // 4、释放锁资源
                pthread_mutex_unlock(&mutex);
        }
 
        pthread_exit(NULL); // 退出线程
}
 
// 定义线程体函数3
void *task3(void *arg)
{
        while (1)
        {
                // 3、获取锁资源
                pthread_mutex_lock(&mutex);
                if (num > 0)
                {
                        usleep(1000);
                        num--;
                        printf("王五买了一张票,剩余:%d\n", num);
 
                }
                else
                {
                        printf("票已经售完,购票失败\n");
                        // 4、释放锁资源
                        pthread_mutex_unlock(&mutex);
                        break;
                }
 
                // 4、释放锁资源
                pthread_mutex_unlock(&mutex);
        }
 
        pthread_exit(NULL); // 退出线程
}
 
/******************主程序**********************/
int main(int argc, const char *argv[])
{
 
        // 2、初始化互斥锁
        pthread_mutex_init(&mutex, NULL);
 
        // 创建两个任务
        pthread_t tid1, tid2, tid3;
 
        if (pthread_create(&tid1, NULL, task1, NULL) != 0)
        {
                printf("task1 创建失败\n");
                return -1;
        }
        if (pthread_create(&tid2, NULL, task2, NULL) != 0)
        {
                printf("task2 创建失败\n");
                return -1;
        }
        if (pthread_create(&tid3, NULL, task3, NULL) != 0)
        {
                printf("task3 创建失败\n");
                return -1;
        }
        // 主线程
        printf("tid1 = %#lx, tid2 = %#lx\n", tid1, tid2);
 
        // 阻塞回收线程资源
        pthread_join(tid1, NULL);
        pthread_join(tid2, NULL);
        pthread_join(tid3, NULL);
 
        // 5、销毁锁资源
        pthread_mutex_destroy(&mutex);
        return 0;
}


2.4    有关死锁的问题


1>    概念:在多线程编程中,死锁是一种情况,其中两个或多个线程被永久阻塞,因为每个线程都在等待其他线程释放它们需要的资源。在C语言中,这通常涉及互斥锁(mutexes),当多个互斥锁被不同的线程以不同的顺序获取时,很容易发生死锁。
2>    死锁产生条件

1. 互斥条件:资源不能被多个线程共享,只能被一个线程在任一时刻所使用。
2. 持有和等待条件:一个线程至少持有一个资源,并且正在等待获取一个当前被其他线程持有的资源。
3. 不可抢占条件:资源不能被强制从一个线程抢占到另一个线程,线程必须自愿释放它的资源。
4. 循环等待条件:存在一个线程(或多个线程)的集合{P1, P2, ..., Pn},其中P1正在等待P2持有的资源,P2正在等待P3持有的资源,依此类推,直至Pn正在等待P1持有的资源。
3>    实例代码

#include <pthread.h>
#include <stdio.h>
 
pthread_mutex_t lock1, lock2;
 
void* thread1(void* arg) {
    pthread_mutex_lock(&lock1);
    sleep(1); // 确保线程2能锁住lock2
    pthread_mutex_lock(&lock2);
    printf("Thread 1\n");
    pthread_mutex_unlock(&lock2);
    pthread_mutex_unlock(&lock1);
    return NULL;
}
 
void* thread2(void* arg) {
    pthread_mutex_lock(&lock2);
    sleep(1); // 确保线程1能锁住lock1
    pthread_mutex_lock(&lock1);
    printf("Thread 2\n");
    pthread_mutex_unlock(&lock1);
    pthread_mutex_unlock(&lock2);
    return NULL;
}
 
int main() {
    pthread_t t1, t2;
    pthread_mutex_init(&lock1, NULL);
    pthread_mutex_init(&lock2, NULL);
    pthread_create(&t1, NULL, thread1, NULL);
    pthread_create(&t2, NULL, thread2, NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_mutex_destroy(&lock1);
    pthread_mutex_destroy(&lock2);
    return 0;
}
4>    如何避免死锁

1. 避免持有和等待:尽可能让线程在开始执行前一次性获取所有必需的资源。
2. 资源排序:规定一个全局顺序来获取资源,并且强制所有线程按这个顺序获取资源。
3. 使用超时:在尝试获取资源时使用超时机制,这样线程在等待过长时间后可以放弃,回退,并重新尝试。
4. 检测死锁并恢复:运行时检测死锁的存在,一旦检测到死锁,采取措施(如终止线程或回滚操作)来解决。
 


2.5    线程同步问题


1>    所谓线程同步,就是将多个线程任务有顺序的执行,由于多个任务有顺序的执行了,那么在同一时刻,对临界资源的访问就只有一个线程了
2>    线程同步文件的经典问题是:生产者消费者问题
对于该问题而言,需要先执行生产者任务,然后执行消费者任务
3>    对于线程同步问题,有两种机制来完成:无名信号量、条件变量
 


2.6    线程同步之无名信号量


1>    无名信号量本质上也是一个特殊的临界资源
2>    无名信号量中维护了一个value值,该值表示能够申请的资源个数,当该值为0时,申请资源的线程将处于阻塞,直到其他线程将该无名信号量中的value值增加到大于0时即可
3>    无名信号量的API如下

1、创建无名信号量:只需要定义一个 sem_t 类型的变量,就创建了一个无名信号量
    sem_t  sem;
2、初始化无名信号量
           #include <semaphore.h>
 
       int sem_init(sem_t *sem, int pshared, unsigned int value);
       功能:完成对无名信号量的初始化工作
       参数1:要被初始化的无名信号量地址
       参数2:无名信号量适用情况
           0:表示多线程之间
           非0:表示多进程间同步
        参数3:无名信号量的资源初始值
        返回值:成功返回0,失败返回-1并置位错误码
 
3、申请资源:P操作,将资源减1操作
           #include <semaphore.h>
 
       int sem_wait(sem_t *sem);
       功能:申请无名信号量中的资源,使得该信号量中的value-1
       参数:无名信号量地址
       返回值:成功返回0,失败返回-1并置位错误码
 
4、释放资源:V操作,将资源加1操作
               #include <semaphore.h>
 
       int sem_post(sem_t *sem);
       功能:将无名信号量中的资源加1操作
       参数:无名信号量地址
       返回值:成功返回0,失败返回-1并置位错误码
 
5、销毁无名信号量
       #include <semaphore.h>
 
       int sem_destroy(sem_t *sem);
       功能:销毁一个无名信号量
       参数:无名信号量地址
       返回值:成功返回0,失败返回-1并置位错误码
 
#include<myhead.h>
 
//1、定义无名信号量
sem_t sem;           //创建一个无名信号量
 
//创建生产者线程
void * task1(void *arg)
{
    int num = 5;
    while(num--)
    {
        sleep(1);
        printf("%#lx:生产了一辆特斯拉\n", pthread_self());
 
        //4、释放资源,表示消费者可以执行了
        sem_post(&sem);
    }
 
    //退出线程
    pthread_exit(NULL);
}
 
//创建消费者线程
void * task2(void *arg)
{
    int num = 5;
    while(num--)
    {
        //3、申请资源
        sem_wait(&sem);
 
        printf("%#lx:消费了一辆特斯拉\n", pthread_self());
        
    }
 
    //退出线程
    pthread_exit(NULL);
}
 
/*******************主程序********************* */
int main(int argc, char const *argv[])
{
 
    //2、初始化无名信号量
    sem_init(&sem, 0, 0);
    //第一个0表示同步用于多线程之间
    //第二个0表示无名信号量的初始资源为0
 
    //创建两个线程
    pthread_t tid1, tid2;
    if(pthread_create(&tid1, NULL, task1, NULL) != 0)
    {
        printf("tid1 create error\n");
        return -1;
    }
    if(pthread_create(&tid2, NULL, task2, NULL) != 0)
    {
        printf("tid2 create error\n");
        return -1;
    }
 
    //阻塞回收线程的资源
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
 
    //5、销毁无名信号量
    sem_destroy(&sem);
    
    return 0;
}
 

 
练习:
写一个程序,创建出三个线程,线程1打印‘A’,线程2打印‘B’,线程3打印‘C’,要求最终打印的结果为:ABCABCABCABCABCABC

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
 
sem_t sem1, sem2, sem3;      //定义三个无名信号量,分别控制三个线程
 
void *thread1_function(void *arg) {
    while (1) {
        sem_wait(&sem1);
        sleep(1);
        printf("A");
        fflush(stdout);
        sem_post(&sem2);
    }
    return NULL;
}
 
void *thread2_function(void *arg) {
    while (1) {
        sem_wait(&sem2);
        sleep(1);
        printf("B");
        fflush(stdout);
        sem_post(&sem3);
    }
    return NULL;
}
 
void *thread3_function(void *arg) {
    while (1) {
        sem_wait(&sem3);
        sleep(1);
        printf("C");
        fflush(stdout);
        sem_post(&sem1);
    }
    return NULL;
}
 
int main() {
    pthread_t thread1, thread2, thread3;
 
    sem_init(&sem1, 0, 1);             //将其中一个线程的初始资源设置成1
    sem_init(&sem2, 0, 0);
    sem_init(&sem3, 0, 0);
 
    pthread_create(&thread1, NULL, thread1_function, NULL);
    pthread_create(&thread2, NULL, thread2_function, NULL);
    pthread_create(&thread3, NULL, thread3_function, NULL);
 
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);
    pthread_join(thread3, NULL);
 
    sem_destroy(&sem1);
    sem_destroy(&sem2);
    sem_destroy(&sem3);
 
    return 0;
}
 


3.7    线程同步之条件变量


1>    条件变量的本质也是一个特殊的临界资源
2>    条件变量中维护了一个队列,想要执行的消费者线程,需要先进入等待队列中,等生产者线程进行唤醒后,依次执行。这样就可以做到一个生产者和多个消费者之间的同步,但是消费者和消费者之间在进入等待队列这件事上是互斥的。
3>    条件变量的API


1、创建条件变量
    只需要定义一个pthread_cond_t类型的变量,就创建了一个条件变量
    pthread_cond_t  cond;
 
2、初始化条件变量
       int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
       功能:初始化一个条件变量
       参数1:条件变量的地址
       参数2:条件变量的属性,一般填NULL
       返回值:     成功返回0,失败返回非0错误码
       
3、唤醒消费者线程                                
 
       int pthread_cond_signal(pthread_cond_t *cond);
       功能:唤醒等待队列中的第一个消费者线程
       参数:条件变量的地址
       返回值: 成功返回0,失败返回非0错误码
 
       int pthread_cond_broadcast(pthread_cond_t *cond);
       功能:唤醒所有等待队列中的消费者线程
       参数:条件变量的地址
       返回值: 成功返回0,失败返回非0错误码
       
4、将消费者线程放入到等待队列中       
 
       int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
       功能:消费者线程进入等待队列中
       参数1:条件变量的地址
       参数2:互斥锁的地址:因为多个消费者线程在进入等待队列上是竞态的
       返回值:     成功返回0,失败返回非0错误码
    
5、销毁条件变量           
        int pthread_cond_destroy(pthread_cond_t *cond);
        功能:销毁一个条件变量
        参数:条件变量的地址
        返回值: 成功返回0,失败返回非0错误码
 
#include <myhead.h>
//1、定义一个条件变量
pthread_cond_t cond;         //创建一个条件变量
//11、创建一个互斥锁
pthread_mutex_t mutex;
 
// 创建生产者线程
void *task1(void *arg)
{
    // int num = 5;
    // while (num--)
    // {
    //     sleep(1);
    //     printf("%#lx:生产了一辆特斯拉\n", pthread_self());
 
    //     //4、唤醒消费者线程开始执行
    //     pthread_cond_signal(&cond);
    // }
 
    sleep(3);        //等待3秒
 
    printf("我生产了5辆特斯拉\n");
 
    //唤醒所有消费者线程
    pthread_cond_broadcast(&cond);
 
 
    // 退出线程
    pthread_exit(NULL);
}
 
// 创建消费者线程
void *task2(void *arg)
{
    //33、获取互斥锁
    pthread_mutex_lock(&mutex);
 
    //3、请求进入等待队列
    pthread_cond_wait(&cond, &mutex);
 
    printf("%#lx:消费了一辆特斯拉\n", pthread_self());
 
    //44、释放锁资源
    pthread_mutex_unlock(&mutex);
 
    // 退出线程
    pthread_exit(NULL);
}
 
/*******************主程序********************* */
int main(int argc, char const *argv[])
{
 
    //2、初始化条件变量
    pthread_cond_init(&cond, NULL);
    //22、初始化互斥锁
    pthread_mutex_init(&mutex, NULL);
 
    // 创建两个线程
    pthread_t tid1, tid2, tid3, tid4, tid5, tid6;
    if (pthread_create(&tid1, NULL, task1, NULL) != 0)
    {
        printf("tid1 create error\n");
        return -1;
    }
    if (pthread_create(&tid2, NULL, task2, NULL) != 0)
    {
        printf("tid2 create error\n");
        return -1;
    }
    if (pthread_create(&tid3, NULL, task2, NULL) != 0)
    {
        printf("tid2 create error\n");
        return -1;
    }
    if (pthread_create(&tid4, NULL, task2, NULL) != 0)
    {
        printf("tid2 create error\n");
        return -1;
    }
    if (pthread_create(&tid5, NULL, task2, NULL) != 0)
    {
        printf("tid2 create error\n");
        return -1;
    }
    if (pthread_create(&tid6, NULL, task2, NULL) != 0)
    {
        printf("tid2 create error\n");
        return -1;
    }
 
    //输出每个线程的线程号
    printf("tid2=%#lx, tid3=%#lx, tid4=%#lx, tid5=%#lx, tid6=%#lx\n", tid2,tid3, tid4, tid5, tid6);
 
    // 阻塞回收线程的资源
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    pthread_join(tid3, NULL);
    pthread_join(tid4, NULL);
    pthread_join(tid5, NULL);
    pthread_join(tid6, NULL);
 
    //销毁条件变量
    pthread_cond_destroy(&cond);
 
    //销毁锁资源
    pthread_mutex_destroy(&mutex);
 
 
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值