Linux系统编程——线程安全

线程同步

互斥量(互斥锁)

保护共享资源的原子访问。访问共享资源的代码段称为临界区。

互斥量的死锁

线程已将目标互斥量锁定,再次进行加锁会造成两种后果:

1、线程死锁,默认情况下线程会发生死锁。
2、加锁调用失败,返回EDEADLK错误

另一种死锁:

线程A:

pthread_mutex_lock(&mutex1);  //阻塞

pthread_mutex_lock(&mutex2);   //阻塞

线程B:

pthread_mutex_lock(&mutex2);  //阻塞

pthread_mutex_lock(&mutex1);

解决办法:

多个线程对互斥量进行操作时,以相同的顺序对互斥量进行锁定

修改后:

线程A:

pthread_mutex_lock(&mutex1);

pthread_mutex_lock(&mutex2);

线程B:

pthread_mutex_lock(&mutex2);

错误行为:

1、对未锁定的互斥量进行解决
2、对其他线程锁定的互斥进行解锁

加锁

#include 

int pthread_mutex_lock(pthread_mutex_t *mutex);

RETURN VALUE 
	成功返回0,失败返回错误编码

解锁

#include 

int pthread_mutex_unlock(pthread_mutex_t *mutex);

RETURN VALUE 
	成功返回0,失败返回错误编码

互斥量的使用:

pthread_mutex_t mutex_lock = PTHREAD_MUTEX_INITALIZER;  //定义互斥量并初始化,互斥量使用前必须进行初始化

pthread_mutex_lock(&mutex_lock); //枷锁
......      //临界区
pthread_mutex_unlock(&mutex_lock); //解锁

代码

#include 
#include 
#include 
 #include 

int global1 = 0;
int global2 = 0;

pthread_mutex_t  mtu_lock =  PTHREAD_MUTEX_INITIALIZER;

void *thread1_entry(void *arg)
{
    int val = 1;
    while (1)
    {
        pthread_mutex_lock(&mtu_lock);
        global1++;
        sleep(1);   //延时阻塞线程,交出CPU控制权thread2线程也得不到执行执行,打印failed情况永远不会出现
        global2++;
        pthread_mutex_unlock(&mtu_lock);
    }
    pthread_exit(&val);
}

void *thread2_entry(void *arg)
{
    int val = 2;
    while (1)
    {
        pthread_mutex_lock(&mtu_lock);
        if (global2 == global1)
        {
            printf("%d %d\r\n", global1, global2);
        }
        else
        {
            printf("failed\r\n");
        }
        pthread_mutex_unlock(&mtu_lock);
        sleep(1);
    }
    pthread_exit(&val);
}

int main(int argc, char **argv)
{
    pthread_t  thread1;
    pthread_t thread2;

    // pthread_mutex_init(&mtu_lock, NULL);   //初始化互斥量

    pthread_create(&thread1, NULL, thread1_entry, NULL); //创建线程
    pthread_create(&thread2, NULL, thread2_entry, NULL);

    pthread_join(thread1, NULL);     //等待线程退出
    pthread_join(thread2, NULL);

    return 0;
}

互斥锁一棒子打死一船人的特性使得其在一些有很多读操作的场合不适合,
从而导致效率低。在这种场合中,对共享资源的保护中,读操作是可以同时进行的,写操作必须互斥保持数据的完整性。

读写锁

读写锁,顾名思义就是有读锁和写锁。读写锁能解决互斥锁上述的缺点。
#include 
#include 
#include 
#include 
#include 

pthread_rwlock_t  rwlock; //定义读写锁

int global = 0;

void *thread1_entry(void *arg)
{
    while (1)
    {
        pthread_rwlock_wrlock(&rwlock); //写数据,加写锁(互斥锁)
        global++;
        printf("%s : %d\r\n", (char *)arg, global);
        pthread_rwlock_unlock(&rwlock);
        // sleep(1);
        pthread_exit(NULL);
    }
    
}

void *thread2_entry(void *arg)
{
    while (1)
    {
        pthread_rwlock_wrlock(&rwlock); //写数据,加写锁(互斥锁)
        global = 100;
        printf("%s : %d\r\n", (char *)arg, global);
        pthread_rwlock_unlock(&rwlock);
        // sleep(1);
        pthread_exit(NULL);
    }
    
}

void *thread3_entry(void *arg)
{
    while (1)
    {
        sleep(1);
        pthread_rwlock_rdlock(&rwlock);      //加读锁(共享锁)
        printf("%s : %d\r\n", (char *)arg, global);
        global++;
        pthread_rwlock_unlock(&rwlock);
        // sleep(1);
        pthread_exit(NULL);
    }
    
}

void *thread4_entry(void *arg)
{
    while (1)
    {
        sleep(1);
        pthread_rwlock_rdlock(&rwlock);      //加读锁(共享锁)
        printf("%s : %d\r\n", (char *)arg, global);
        pthread_rwlock_unlock(&rwlock);
    
        pthread_exit(NULL);
    }
    
}

int main(int argc, char **argv)
{
    pthread_t thread1;
    pthread_t thread2;
    pthread_t thread3;
    pthread_t thread4;

    pthread_rwlock_init(&rwlock, NULL); //初始化读写锁

    pthread_create(&thread1, NULL, thread1_entry, "thread1");
    pthread_create(&thread2, NULL, thread2_entry, "thread2");
    pthread_create(&thread3, NULL, thread3_entry, "thread3");
    pthread_create(&thread4, NULL, thread4_entry, "thread4");

    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);
    pthread_join(thread3, NULL);
    pthread_join(thread4, NULL);

    return 0;
}

线程3和线程4加的是读锁(共享锁),所以无论何时两者输出的共享变量global的值都是一样的


条件变量

作用逻辑:

一个线程根据某个共享变量的状态变化通知其他线程,让其他线程阻塞等待通知

条件变量需要配合互斥锁或者信号量来使用
条件变量用来对共享变量的状态变化发出通知
互斥锁用来对共享变量进行互斥

定义条件变量

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;  //定义条件变量并初始化

初始化、销毁条件变量

当不需要使用条件变量时,应要进行销毁

#include 

int pthread_cond_init(pthread_cond_t *restrict cond, 
                       const pthread_condattr_t *restrict attr)
int pthread_cond_destroy(pthread_cond_t *cond);
cond —— 条件变量
attr —— 条件变量属性,一般设置为NULL
    
RETURN VALUE
	成功 : 0
    失败 : -1

条件变量—发出通知

#include 

int pthread_cond_signal(pthread_cond_t *cond); //只保证唤醒一条等待条件变量阻塞的线程
//broadcast-广播
int pthread_cond_broadcast(pthread_cond_t *cond);//唤醒所有等待条件变量的线程 
cond —— 条件变量
RETURN VALUE 
	成功 :0
	失败 :-1

等待条件变量

#include 

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

int pthread_cond_timewait(pthread_cond_t *cond, pthread_mutex_t *mutex
                          const struct timespec *abstime);
cond —— 条件变量
mutex —— 互斥锁
abstime —— 超时时间限制,超时立即返回
    
RETURN VALUE 
	成功 :0
	失败 :-1

代码

(一)

#include 
#include 
#include 
#include 
#include 
#include 

int global = 0;  

pthread_mutex_t mtu_lock = PTHREAD_MUTEX_INITIALIZER; //定义互斥锁并初始化
pthread_cond_t  cond = PTHREAD_COND_INITIALIZER; //定义条件变量并初始化

//线程入口函数
void *thread1_entery (void *arg)
{   
    while (1)
    {
        pthread_mutex_lock(&mtu_lock);
        global++;
        printf("thread1 global : %d\r\n", global);
        pthread_cond_signal(&cond);  //通知唤醒等待条件变量的线程
        if (global >= 10)
        {
            pthread_mutex_unlock(&mtu_lock); //解锁
            pthread_exit(NULL); //退出线程
        }
        pthread_mutex_unlock(&mtu_lock);
        sleep(1);
    }    
}

void *thread2_entery (void *arg)
{
    while (1)
    {
        pthread_mutex_lock(&mtu_lock);
        while (global < 10)
        {
            pthread_cond_wait(&cond, &mtu_lock);//等待条件变量并解锁
        }
        printf("thread2 global : %d\r\n", global);
        pthread_mutex_unlock(&mtu_lock); //退出前先解锁
        pthread_exit(NULL);    
    }  
}

int main(int argc, char **argv)
{
    pthread_t thread1;  //线程tid
    pthread_t thread2;

    int retval;

    retval =  pthread_create(&thread1, NULL, thread1_entery, NULL); //创建线程
    if (retval < 0)
        exit(0);
    retval =  pthread_create(&thread2, NULL, thread2_entery, NULL);
    if (retval < 0)
    {
        pthread_cancel(thread1);
        exit(0);
    }
        
    pthread_join(thread1, NULL); //等待线程退出
    pthread_join(thread2, NULL); 

    pthread_mutex_destroy(&mtu_lock); //销毁互斥锁
    pthread_cond_destroy(&cond); //销毁条件变量

    printf("end\r\n");

    return 0;
}

(二)

#include 
#include 
#include 
#include 
#include 
#include 

int global = 0;  

pthread_mutex_t mtu_lock = PTHREAD_MUTEX_INITIALIZER; 
pthread_cond_t  cond = PTHREAD_COND_INITIALIZER; 

void *thread1_entery (void *arg)
{   
    while (1)
    {
        pthread_mutex_lock(&mtu_lock);
        global++;
        printf("thread1 global : %d\r\n", global);
        
        if (global >= 10)
        {
            pthread_cond_signal(&cond);  
            pthread_mutex_unlock(&mtu_lock);
            pthread_exit(NULL); 
            break;
        }
        pthread_mutex_unlock(&mtu_lock);
        sleep(1);
    }   
}

void *thread2_entery (void *arg)
{
    while (1)
    {
        pthread_mutex_lock(&mtu_lock);
    
        pthread_cond_wait(&cond, &mtu_lock);

        printf("thread2 global : %d\r\n", global);
         
        pthread_mutex_unlock(&mtu_lock);   
        pthread_exit(NULL);    
    }   
}

int main(int argc, char **argv)
{
    pthread_t thread1;
    pthread_t thread2;

    int retval;

    retval =  pthread_create(&thread1, NULL, thread1_entery, NULL);
    if (retval < 0)
        exit(0);
    retval =  pthread_create(&thread2, NULL, thread2_entery, NULL);
    if (retval < 0)
    {
        pthread_cancel(thread1);
        exit(0);
    }
        
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    pthread_mutex_destroy(&mtu_lock); //销毁互斥锁
    pthread_cond_destroy(&cond); //销毁条件变量

    printf("end\r\n");

    return 0;
}

两部分代码执行结果一致


可重入函数

可重入——顾名思义就是可以重复进入,主要用在多任务环境中

概念

简单地说可重入函数就是可以被中断的函数,任何时刻中断它或者OS调度执行其他任务再返回执行它不会出现什么错误

所有库函数基本都是可重入函数,除了以下函数:
Linux系统编程——线程安全_第1张图片

多任务情况下调用这些函数可能产生不一样的结果。

不可重入函数产生的主要原因:

1、函数使用了共享资源、环境变量
2、调用了其他不可重入函数
3、函数的执行结果与硬件资源有关

编写线程安全的可重入函数原则:

1、不使用任何静态数据,只用局部变量或堆内存
2、不调用任何非线程安全的不可重入函数

你可能感兴趣的:(Linux系统编程)