linux线程

线程互斥锁

一、关于线程互斥锁

线程的主要优势在于能够通过全局变量来共享信息,不过这种便捷的共享是有代价的 :
必须确保多个线程不会同时修改同一变量
某一线程不会读取正由其他线程修改的变量,实际就是 不能让两个线程同时对临界区进行访问
线程互斥锁则可以用于解决多线程资源竞争问题
示例代码:
创建两个子线程,定义一个全局变量 global = 0, 子线程对此全局变量进行加 1 操作
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
int global = 0;
//线程执行函数
void* do_thread(void* argv)
{
    //循环对global进行加1操作
    int loops = *(int*)argv;
    for(int i=0;i<loops;i++)
    {
        int temp = global;
        temp++;
        global=temp;
    }
    pthread_exit(NULL);
}
int main(int argc,char* argv[])
{
    if(argc !=2)
    {
        fprintf(stderr,"arguments must be 2:< cmd > <count>\n");
        exit(EXIT_FAILURE);
    }
    // 获取循环次数
    int loopCount = atoi(argv[1]);
    // 循环的方式创建两个线程
    pthread_t tids[2]={0};
    int err;
    for(int i=0;i<2;i++)
    {
        err = pthread_create(tids+i,NULL,do_thread,&loopCount);
        if(err!=0)
        {
            fprintf(stderr,"pthread_create
            failed:%s\n",strerror(err));
            exit(EXIT_FAILURE);
        }
    }
    pthread_join(tids[0],NULL);
    pthread_join(tids[1],NULL);
    // 打印全局变量
    printf("global=%d\n",global);
    return 0;
}
将数字字符串转换为整数
int atoi(const char *nptr);

二、线程互斥锁

1. 线程互斥锁工作机制
当线程 A 获得锁,另外一个线程 B 在获得锁时则会阻塞,直到线程 A 释放锁,线程 B 才会获得锁。
2. 线程互斥锁工作原理
本质上是一个 pthread_mutex_t 类型的变量,假设名为 v
v = 1, 则表示当前临界资源可以竞争访问 ,得到互斥锁的线程则可以访问,此时 v = 0
v = 0, 则表示临界资源正在被某个线程访问,其他线程则需要等待
3. 线程互斥锁的特点
互斥锁是一个 pthread_mutex_t 类型的变量,就代表一个 互斥锁
如果两个线程访问的是同一个 pthread_mutex_t 变量,那么它们访问了同一个互斥锁
对应的变量定义在 pthreadtypes.h 头文件中,是一个共用体中包含一个结构体

三、线程互斥锁的初始化

线程互斥锁的初始化方式主要分为两种
1. 静态初始化
定义 pthread_mutex_t 类型的变量,然后对其初始化为 PTHREAD_MUTEX_INITIALIZER
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER
2. 动态初始化
动态初始化主要涉及两个函数 pthread_mutex_init 函数 与 pthread_mutex_destroy 函数
pthread_mutex_init 函数
获取锁的函数: pthread_mutex_lock
函数头文件
#include <pthread.h>
函数原型
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const
pthread_mutexattr_t *restrict attr);
函数功能
初始化线程互斥锁
函数参数
mutex: 线程互斥锁对象指针
attr: 线程互斥锁属性
函数返回值
成功 : 返回 0
失败 : 返回错误编码
pthread_mutex_destroy 函数
函数头文件
#include <pthread.h>
函数原型
int pthread_mutex_destroy(pthread_mutex_t *mutex);
函数功能
销毁线程互斥锁
函数参数
mutex: 线程互斥锁指针
函数返回值
成功 : 返回 0
失败 : 返回错误编码
四、线程互斥锁的操作
线程互斥锁的操作主要分为 获取锁 (lock) 释放锁 (unlock)
获取锁的函数: pthread_mutex_lock
函数头文件
#include <pthread.h>
函数原型
int pthread_mutex_lock(pthread_mutex_t *mutex);
函数功能
将互斥锁进行锁定,如果已经锁定,则阻塞线程
函数参数
mutex: 线程互斥锁指针
函数返回值
成功 : 返回 0
失败 : 返回错误码
释放锁的函数: pthread_mutex_unlock
函数头文件
#include <pthread.h>
函数原型
int pthread_mutex_unlock(pthread_mutex_t *mutex);
函数功能
解除互斥锁锁定状态,解除后,所有线程可以重新竞争锁
函数参数
mutex: 线程互斥锁对象的指针
函数返回值
成功 : 返回 0
失败 : 返回错误码
五、示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
int global = 0 ;
// 互斥锁静态初始化
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER ;
// 线程执行函数
void* do_thread ( void* argv )
{
// 循环对 global 进行加 1 操作
int loops = * ( int* ) argv ;
for ( int i = 0 ; i < loops ; i ++ )
{
// 获取锁,一旦获取成功,则获取临界资源的访问资格,否则会阻塞当前线程
pthread_mutex_lock ( & mtx );
int temp = global ;
temp ++ ;
global = temp ;
// 释放锁
pthread_mutex_unlock ( & mtx );
}
pthread_exit ( NULL );
}
int main ( int argc , char* argv [])
{
if ( argc != 2 )
{
fprintf ( stderr , "arguments must be 2:< cmd > <count>\n" );
exit ( EXIT_FAILURE );
}
// 获取循环次数
int loopCount = atoi ( argv [ 1 ]);
// 循环的方式创建两个线程
pthread_t tids [ 2 ] = { 0 };
int err ;
for ( int i = 0 ; i < 2 ; i ++ )
{
err = pthread_create ( tids + i , NULL , do_thread , & loopCount );
if ( err != 0 )
{
fprintf ( stderr , "pthread_create
failed:%s\n" , strerror ( err ));
exit ( EXIT_FAILURE );
}
}
pthread_join ( tids [ 0 ], NULL );
pthread_join ( tids [ 1 ], NULL );
// 打印全局变量
printf ( "global=%d\n" , global );
return 0 ;
}
<1> 动态初始化线程互斥锁的使用
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
int global = 0 ;
// 互斥锁变量
pthread_mutex_t mtx ;
// 线程执行函数
void* do_thread ( void* argv )
{
// 循环对 global 进行加 1 操作
int loops = * ( int* ) argv ;
for ( int i = 0 ; i < loops ; i ++ )
{
// 获取锁
pthread_mutex_lock ( & mtx );
int temp = global ;
temp ++ ;
global = temp ;
// 释放锁
pthread_mutex_unlock ( & mtx );
}
pthread_exit ( NULL );
}
int main ( int argc , char* argv [])
{
if ( argc != 2 )
{
fprintf ( stderr , "arguments must be 2:< cmd > <count>\n" );
exit ( EXIT_FAILURE );
}
pthread_mutex_init ( & mtx , NULL );
// 获取循环次数
int loopCount = atoi ( argv [ 1 ]);
// 循环的方式创建两个线程
pthread_t tids [ 2 ] = { 0 };
int err ;
for ( int i = 0 ; i < 2 ; i ++ )
{
err = pthread_create ( tids + i , NULL , do_thread , & loopCount );
if ( err != 0 )
{
fprintf ( stderr , "pthread_create
failed:%s\n" , strerror ( err ));
exit ( EXIT_FAILURE );
}
}
pthread_join ( tids [ 0 ], NULL );
pthread_join ( tids [ 1 ], NULL );
pthread_mutex_destroy ( & mtx );
// 打印全局变量
printf ( "global=%d\n" , global );
return 0 ;
}

线程同步

一、线程同步的概念
线程同步 :是指在互斥的基础上,通过其它机制实现访问者对 资源的有序访问
条件变量 :线程库提供的专门针对线程同步的机制
线程同步比较典型的应用场合就是 生产者与消费者
二、生产者与消费者模型原理
1. 在这个模型中,包括 生产者线程 消费者线程 。通过线程来模拟多个线程同步的过程
2. 在这个模型中 , 需要以下组件
仓库 : 用于存储产品,一般作为共享资源
生产者线程 : 用于生产产品
消费者线程 : 用于消费产品
3. 原理
当仓库没有产品时,则消费者线程需要等待,直到有产品时才能消费
当仓库已经装满产品时,则生产者线程需要等待,直到消费者线程消费产品之后
三、生产者与消费者模型同步
示例:基于互斥锁实现生产者与消费者模型
要求: 1 、主线程作为消费者线程 2 n 个子线程作为生产者线程
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
// 静态互斥锁
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static int product_number=0;
void* thread_handle(void* arg)
{
    // 生产产品的数量
    int cnt = atoi((char*)arg);
    for(int i=1;i<=cnt;i++)
    {
        // 获取锁
        pthread_mutex_lock(&mutex);
        printf("thread[%ld] produces a product, the number of
        products is %d.\n",pthread_self(),++product_number);
        // 释放锁
        pthread_mutex_unlock(&mutex);
    }
    pthread_exit(NULL);
}
// command format[./a.out 1 2 3],1 2 3表示生产者线程生产的产品数量
int main(int argc,char* argv[])
{
    int total_of_production= 0;// 生产产品的数量
    int total_of_consumption=0;// 消费产品的数量
    if(argc < 2)
    {
        fprintf(stderr,"command argument: ./a.out <...product
        quantity>\n");
        exit(EXIT_FAILURE);
    }
    pthread_t tid;
    for(int i=1;i<argc;i++)
    {
        total_of_production += atoi(argv[i]);
        int err = pthread_create(&tid,NULL,thread_handle,
        (void*)argv[i]);
        if(err > 0)
        {
            fprintf(stderr,"pthread_create
            error:%s\b",strerror(err));
            exit(EXIT_FAILURE);
        }
    }
    // 消费产品
    while(1)
    {
        pthread_mutex_lock(&mutex);
        while(product_number > 0)
        {
            printf("consumption a product ,the number of products is %d.\n",--product_number);
        total_of_consumption++;
        sleep(1);
        }
    pthread_mutex_unlock(&mutex);
    if(total_of_production == total_of_consumption)
    {
        break;
    }
    }
    return 0;
}

条件变量

一、条件变量简介

上面示例代码的不足:
主线程 ( 消费者线程 ) 需要不断查询是否有产品可以消费,如果没有产品可以消费,也在运行程序,包括获
得互斥锁、判断条件、释放互斥锁,非常消耗 cpu 资源。
使用条件变量进行修改
条件变量 允许一个线程就某个共享变量的状态变化通知其他线程,并让其他线程等待这一通知

二、条件变量初始化

条件变量的本质为 pthread_cond_t 类型的变量,其他线程可以阻塞在这个条件变量上,或者唤醒阻塞
在这个条件变量上的线程。
typedef union
{
struct __pthread_cond_s __data;
char __size[__SIZEOF_PTHREAD_COND_T];
__extension__ long long int __align;
} pthread_cond_t;
条件变量的初始化分为静态初始化与动态初始化
1. 静态初始化
                 pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
2. 动态初始化
pthread_cond_init 函数
函数头文件
#include <pthread.h>
函数原型
int pthread_cond_init(pthread_cond_t *restrict cond,const
pthread_condattr_t *restrict attr);
函数功能
初始化条件变量
函数参数
cond:条件变量指针
attr:条件变量属性
函数返回值
成功:返回0
失败:返回错误码
pthread_cond_destroy函数
函数头文件
#include <pthread.h>
函数原型
int pthread_cond_destroy(pthread_cond_t *cond);
函数功能
销毁条件变量
函数参数
cond:条件变量指针
函数返回值
成功:返回0
失败:返回错误码

三、条件变量原理

3.1 原理分析
基于条件变量的阻塞与唤醒,具体的原理如下图
step 1 :消费者线程判断消费条件是否满足 ( 仓库是否有产品 ) ,如果有产品则可以消费,然后解锁
step 2 :当条件满足时 ( 仓库产品数量为 0) ,则调用 pthread_cond_wait 函数 , 这个函数具体做的事
情如下 :
在线程睡眠之前,对互斥锁解锁
让线程进入到睡眠状态
等待条件变量收到信号时 ,该函数重新竞争锁,并获取锁
step 3 :重新判断条件是否满足,如果满足,则继续调用 pthread_cond_wait 函数
step 4 :唤醒后,从 pthread_cond_wait 返回,条件不满足,则正常消费产品
step 5 :释放锁,整个过程结束
示例代码:基于条件变量实现生产者与消费者模型 ( 多个生产者对应一个消费者 )
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
// 静态互斥锁
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//条件变量的静态初始化
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
static int product_number=0;
void* thread_handle(void* arg)
{
    // 生产产品的数量
    int cnt = atoi((char*)arg);
    for(int i=1;i<=cnt;i++)
    {
        // 获取锁
        pthread_mutex_lock(&mutex);
        printf("thread[%ld] produces a product, the number of products is %d.\n",pthread_self(),++product_number);
        // 唤醒消费者线程
        pthread_cond_signal(&cond);
        // 释放锁
        pthread_mutex_unlock(&mutex);
    }
    pthread_exit(NULL);
}
// command format[./a.out 1 2 3],1 2 3表示生产者线程生产的产品数量
int main(int argc,char* argv[])
{
    int total_of_production= 0;// 生产产品的数量
    int total_of_consumption=0;// 消费产品的数量
    if(argc < 2)
    {
        fprintf(stderr,"command argument: ./a.out <...product quantity>\n");
        exit(EXIT_FAILURE);
    }
    pthread_t tid;
    for(int i=1;i<argc;i++)
    {
        total_of_production += atoi(argv[i]);
        int err = pthread_create(&tid,NULL,thread_handle,(void*)argv[i]);
        if(err > 0)
        {
            fprintf(stderr,"pthread_create error:%s\b",strerror(err));
exit(EXIT_FAILURE);
        }    
    }
// 消费产品
    while(1)
    {
        pthread_mutex_lock(&mutex);
        // 使用条件变量进行阻塞
        while(product_number==0)    
        {
            pthread_cond_wait(&cond,&mutex);
        }
        while(product_number > 0)
       {
            printf("consumption a product ,the number of products is %d.\n",--product_number);
            total_of_consumption++; sleep(1);
}
            pthread_mutex_unlock(&mutex);
            if(total_of_production == total_of_consumption)
            {
                break;
            }
        }
    return 0;
}
3.2 pthread_cond_wait
                
函数头文件
#include <pthread.h>
函数原型
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict
mutex);
函数功能
阻塞线程,等待唤醒
函数参数
cond:条件变量指针
mutex:相关联的互斥锁指针
函数返回值
成功:返回 0
失败:返回错误码
条件变量需要与互斥锁结合使用,先获得锁才能进行条件变量的操作
调用函数后会释放锁,并阻塞线程
一旦线程唤醒,需要重新竞争锁,重新获得锁之后, pthread_cond_wait 函数返回
3.3 pthread_cond_signal pthread_cond_broadcast
pthread_cond_signal 函数
函数头文件
#include <pthread.h>
函数原型
int pthread_cond_signal(pthread_cond_t *cond);
函数功能
唤醒所有阻塞在某个条件变量上的线程
函数返回值
成功:返回0
失败:返回错误码
pthread_cond_broadcast 函数
函数头文件
#include <pthread.h>
函数原型
int pthread_cond_broadcast(pthread_cond_t *cond);
函数功能
唤醒所有阻塞在某个条件变量上的线程
函数参数
cond: 条件变量指针
函数返回值
成功 : 返回 0
失败 : 返回错误码
pthread_cond_signal 函数主要 适用等待线程都在执行完全相同的任务
pthread_cond_broadcast 函数 主要适用等待线程都执行不相同的任务
条件变量并不保存状态信息,只是传递应用程序状态信息的一种通讯机制,发送信号时若无任何线
程在等待该条件变量,则会被忽略
条件变量代表是一个通讯机制,用于传递通知与等待通知,用户可以设定条件来发送或者等待通知
四、问题
4.1 为什么条件变量需要与互斥锁结合起来使用 ?
答:防止在调用 pthread_cond_wait 函数等待一个条件变量收到唤醒信号,另外一个线程发送信号在第
一个线程实际等待它之前,也就是说线程还没有完全进入到睡眠状态,其他线程发送唤醒信号。
4.2 在判断条件时 , 为什么需要使用 while(number == 0), 而不是 if()
答:防止虚假唤醒。
能够唤醒的情况如下 :
被信号唤醒,并非由条件满足而唤醒
条件变量状态改变时,一次唤醒多个线程,但是被其他线程先消费完产品,等到当前线程执行时,
条件已经不满足
补充:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值