一、同步

1.1 线程不同步会产生的问题

  如下代码和结果所示,当线程没有同步时,多个线程抢占CPU资源,可能导致各种问题的发生。

/*
    使用多线程实现卖票:
    有三个窗口,一共是100张票


*/

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

int tickets=0;

void* saletic(void*arg){
    //卖票
    
    while(tickets<100){
        usleep(6000);
        printf("%ld 正在卖第%d张门票\n", pthread_self(), tickets);
        ++tickets;
    }
    return NULL;
}
int main(){
    //创建3个子线程
    pthread_t tid1;
    pthread_t tid2;
    pthread_t tid3;
    pthread_create(&tid1, NULL, saletic, NULL);
    pthread_create(&tid2, NULL, saletic, NULL);
    pthread_create(&tid3, NULL, saletic, NULL);

    //回收子线程,阻塞的函数
    // pthread_join(tid1,NULL);
    // pthread_join(tid2,NULL);
    // pthread_join(tid3,NULL);

    //或者设置线程分离
    pthread_detach(tid1);
    pthread_detach(tid2);
    pthread_detach(tid3);
    //退出主线程
    pthread_exit(NULL);




    return 0;
}

 

 1.2 线程同步概念

  线程的主要优势在于,能够通过全局变量来共享信息。不过,这种便捷的共享是有代价的:必须确保多个线程不会同时修改同一变量,或者某一线程不会读取正在由其他线程修改的变量。临界区是指访问某一共享资源的代码片段,并且这段代码的执行应为原子操作,也就是同时访问同一共享资源的其他线程不应中断该片段的执行。

  线程同步:即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作,而其他线程则处于等待状态。线程同步会降低线程的并发效率,但是对于保证数据的安全性,是必须的。

二、锁

2.1互斥锁(量)概念

  为避免线程更新共享变量时出现问题,可以使用互斥量(mutex是mutual exclusion的缩写)来确保同时仅有一个线程可以访问某项共享资源。可以使用互斥量来保证对任意共享资源的原子访问。

  互斥量有两种状态∶已锁定(locked)和未锁定(unlocked)。任何时候,至多只有一个线程可以锁定该互斥量。试图对已经锁定的某一互斥量再次加锁,将可能阻塞线程或者报错失败,具体取决于加锁时使用的方法。
  一旦线程锁定互斥量,随即成为该互斥量的所有者,只有所有者才能给互斥量解锁。一般情况下,对每一共享资源(可能由多个相关变量组成)会使用不同的互斥量,每一线程在访问同一资源时将采用如下协议︰

  --针对共享资源锁定胡互斥量

  --访问共享锁

  --对互斥量解锁

 2.2 互斥锁相关函数及同步代码

/*

互斥量的类型pthread mutex_t

int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr) ;
    功能:初始化互斥量
    参数:
        -mutex:需要初始化的互斥量变量
        -attr:互斥量相关属性,NULL
    -restrict:C语言修饰符,被修饰的指针不能由另外的指针进行操作
int pthread_mutex_destroy (pthread _mutex_t *mutex);
    功能:释放互斥量
    参数:
        -mutex:互斥量
int pthread_mutex_lock (pthread _mutex_t *mutex) ;
    功能:加锁、阻塞,若有一个线程加锁,其他线程加锁等待
int pthread_mutex_trylock (pthread_mutex_t *mutex);
    功能:尝试加锁,如果失败不会阻塞,会直接返回
int pthread_mutex_unlock (pthread_mutex_t *mutex);
    功能:解锁

*/


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

int tickets=0;
//创建一个互斥量

pthread_mutex_t mutex;
void* saletic(void*arg){

    //卖票
    
    while(1){
        //加锁
        pthread_mutex_lock(&mutex);
        if(tickets<1000){
            usleep(60);
            printf("%ld 正在卖第%d张门票\n", pthread_self(), tickets);
            ++tickets;
        }
        else {
            //解锁
            pthread_mutex_unlock(&mutex);
            break;
        }
        //解锁
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}
int main(){
    
    //初始化互斥量
    pthread_mutex_init(&mutex,NULL);

    //创建3个子线程
    pthread_t tid1;
    pthread_t tid2;
    pthread_t tid3;
    pthread_create(&tid1, NULL, saletic, NULL);
    pthread_create(&tid2, NULL, saletic, NULL);
    pthread_create(&tid3, NULL, saletic, NULL);

    //回收子线程,阻塞的函数
    // pthread_join(tid1,NULL);
    // pthread_join(tid2,NULL);
    // pthread_join(tid3,NULL);

    //或者设置线程分离
    pthread_detach(tid1);
    pthread_detach(tid2);
    pthread_detach(tid3);
    //退出主线程
    pthread_exit(NULL);

    pthread_mutex_destroy(&mutex);


    return 0;
}

 2.3 死锁

  有时,一个线程需要同时访问两个或更多不同的共享资源,而每个资源又都由不同的互斥量管理。当超过一个线程加锁同一组互斥量时,就有可能发生死锁两个或两个以上的进程在执行过程中,因争夺共享资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁。

  可能产生死锁的场景:1.忘记释放锁,2.重复加锁(相同的锁),3.多线程多锁,抢占锁资源。下图为多线程多锁的场景

 

  

   以下是多线程多锁的死锁实例代码:

/*
    死锁
*/
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include <pthread.h>

int tickets=0;
//创建两个互斥量
pthread_mutex_t mutex1;
pthread_mutex_t mutex2;

void* workA(void*arg){
        //加锁1
        pthread_mutex_lock(&mutex1);
        sleep(1);
        //加锁2
        pthread_mutex_lock(&mutex2);

        printf("work A.......\n");
        //解锁2
        pthread_mutex_unlock(&mutex2);
        //解锁1
        pthread_mutex_unlock(&mutex1);
    return NULL;
}

void* workB(void*arg){
    //加锁1
    pthread_mutex_lock(&mutex2);
    sleep(1);
    //加锁2
    pthread_mutex_lock(&mutex1);

    printf("work B.......\n");
        //解锁2
    pthread_mutex_unlock(&mutex1);
        //解锁1
    pthread_mutex_unlock(&mutex2);
    return NULL;
}
int main(){
    
    //初始化两个互斥量
    pthread_mutex_init(&mutex1,NULL);
    pthread_mutex_init(&mutex2,NULL);
    //创建2个子线程
    pthread_t tid1;
    pthread_t tid2;

    pthread_create(&tid1, NULL, workA, NULL);
    pthread_create(&tid2, NULL, workB, NULL);

    //设置线程分离
    pthread_detach(tid1);
    pthread_detach(tid2);
    //退出主线程
    pthread_exit(NULL);
    //释放锁
    pthread_mutex_destroy(&mutex1);
    pthread_mutex_destroy(&mutex2);

    return 0;
}

 2.4 读写锁

  当有一个线程已经持有互斥锁时,互斥锁将所有试图进入临界区的线程都阻塞住。但是考虑一种情形,当前持有互斥锁的线程只是要读访问共享资源,而同时有其它几个线程也想读取这个共享资源,但是由于互斥锁的排它性,所有其它线程都无法获取锁,也就无法读访问共享资源了,但是实际上多个线程同时读访问共享资源并不会导致问题。在对数据的读写操作中,更多的是读操作,写操作较少,例如对数据库数据的读写应用。为了满足当前能够允许多个读出,但只允许一个写入的需求,线程提供了读写锁来实现。

  读写锁有以下特点:

  ----如果有其他线程读数据,则允许其他线程执行读操作,但不允许写操作。

  ----如果有其他线程写数据,则其他线程都不允许读、写操作。

  ----写是独占的,写的优先级高。

  以下代码为读写锁案例:

/*

    读写锁的类型 pthread_rwlock_t
    int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t * restrict attr) ;

    int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

    int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

    int pthread_rwlock_tryrdlock (pthread_rwlock_t *rwlock);

    int pthread_rwlock_wrlock (pthread_rwlock_t *rwlock ) ;

    int pthread_rwlock_trywrlock (pthread_rwlock_t *rwlock);

    int pthread_rwlock_unlock (pthread_rwlock_t *rwlock);
*/
/*
    案例:创建8个线程,操作同一个全局变量。
        3个线程不定时写一个全局变量,其余5个线程不定时地读这个全局变量
*/

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include <pthread.h>
//创建一个共享数据
int num=1;
pthread_rwlock_t rwlock;

void* writeNum(void*arg){
    while(1){
        pthread_rwlock_wrlock(&rwlock);
        num++;
        printf("++++write id: %ld , num : %d\n", pthread_self(), num);
        pthread_rwlock_unlock(&rwlock);
        usleep(100);
    }

    return NULL;
}
void* readNum(void*arg){
    while(1){
        pthread_rwlock_rdlock(&rwlock);
        printf("=====read id: %ld , num : %d\n", pthread_self(), num);
        pthread_rwlock_unlock(&rwlock);
        usleep(100);
    }
    return NULL;
}



int main(){
    pthread_rwlock_init(&rwlock,NULL);
    //3个写线程
    pthread_t wtids[3];
    //5个读线程
    pthread_t rtids[5];

    for(int i=0;i<3;++i){
        pthread_create(&wtids[i], NULL, writeNum, NULL);
    }
    for(int i=0;i<5;++i){
        pthread_create(&rtids[i], NULL, readNum, NULL);
    }
    //设置线程分离
    for(int i=0;i<3;++i){
        pthread_detach(wtids[i]);
    }
    for(int i=0;i<5;++i){
        pthread_detach(rtids[i]);
    }
    pthread_rwlock_destroy(&rwlock);
    pthread_exit(NULL);
    return 0;
}