线程同步与互斥
  • mutex(互斥量)

大部分情况下,线程使用的数据都是局部变量,变量的地址空间在线程的栈空间内。这种情况下,变量归属于单个线程,其他线程无法获得这种变量。

但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互。

多个线程并发的操作共享变量,会带来一些问题。

代码实例:

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

int ticket = 100;
void *route(void *arg){
    char *id = (char*)arg;
    while(1){
        if(ticket > 0){
            usleep(1000);
            printf("%s sells ticket:%d\n",id,ticket);
            ticket--;
        }
        else{
            break;
        }
    }
}

int main(){
    pthread_t t1,t2,t3,t4;

    pthread_create(&t1,NULL,route,"thread 1");
    pthread_create(&t2,NULL,route,"thread 2");
    pthread_create(&t3,NULL,route,"thread 3");
    pthread_create(&t4,NULL,route,"thread 4");

    pthread_join(t1,NULL);
    pthread_join(t2,NULL);
    pthread_join(t3,NULL);
    pthread_join(t4,NULL);
}

结果演示:


. . . . . . 


为什么操作结果并不是我们预想的那样呢?

1.if语句判断条件为真以后,代码可以并发的切换到其他线程。

2.usleep模拟漫长的业务过程,在这个漫长的业务过程中,可能有很多个线程会进入该代码段。

3.--ticket操作本身就不是一个原子操作。

取出ticket的汇编代码:objdump   -d   ticket

部分汇编代码如下:


ticket--中的--操作并不是一个原子操作,而是对应三条汇编指令:

1.load:将共享变量ticket从内存加载到寄存器中。

2.update:更新寄存器里面的值,执行-1操作。

3.store:将新值从寄存器写回共享变量ticket的内存地址。

要解决以上问题,需要做到三点:

1.代码必须具有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区。

2.如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临界区。

3.如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区。

要做到这三点,本质上就是需要一把锁。Linux上提供的这把锁叫互斥量。

加锁的目的:保证临界资源的安全性。

线程对全局变量要进行加锁操作。

                       

  • 互斥量的接口

初始化互斥量:

1.静态分配:pthread_mutex_t   mutex = PTHREAD_INITIALIZER

2.动态分配:

    int   pthread_mutex_init(pthread_mutex_t   *restrict   mutex,const   pthread_mutexattr_t   *restrict   attr);

    参数:

            mutex:要初始化的互斥量

            attr:NULL

销毁互斥量:

    销毁互斥量需要注意:

        1.使用PTHREAD_MUTEX_INITIALIZER初始化的互斥量不需要销毁。

        2.不要销毁一个已经加锁的互斥量。

        3.已经销毁的互斥量,要确保后面不会有线程再尝试加锁。

    函数原型:int   pthread_mutex_destroy(pthread_mutex_t   *mutex);

互斥量加锁和解锁:

int   pthread_mutex_lock(pthread_mutex_t   *mutex);

int   pthread_mutex_unlock(pthread_mutex_t   *mutex);

返回值:失败返回0;失败返回错误号。

调用pthread_lock时,可能会遇到以下情况:

    1.互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功。

    2.发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_lock调用会陷入阻塞,等待互斥量解锁。

注:加锁定义的也是全局变量,可以被多个线程访问,属于临界资源。用一个临界资源去保护另外的临界资源的前提是:加锁、解锁的操作必须是原子性的。

上面售票系统的改进版:

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

int ticket = 100;
pthread_mutex_t mutex;

void *route(void *arg){
    char *id = (char*)arg;
    while(1){
        pthread_mutex_lock(&mutex);
        if(ticket > 0){
            usleep(1000);
            printf("%s sells ticket:%d\n",id,ticket);
            ticket--;
            pthread_mutex_unlock(&mutex);
        }
        else{
            pthread_mutex_unlock(&mutex);
            break;
        }
    }
}

int main(){
    pthread_t t1,t2,t3,t4;

    pthread_create(&t1,NULL,route,"thread 1");
    pthread_create(&t2,NULL,route,"thread 2");
    pthread_create(&t3,NULL,route,"thread 3");
    pthread_create(&t4,NULL,route,"thread 4");

    pthread_join(t1,NULL);
    pthread_join(t2,NULL);
    pthread_join(t3,NULL);
    pthread_join(t4,NULL);

    pthread_mutex_destroy(&mutex);
}

结果演示:


. . . . . . 


  • 条件变量

当一个线程互斥地访问某个变量时,它可能在其它线程改变状态之前什么也做不了。

例如一个线程访问队列时,发现队列为空,它就只能等待,直到其它线程将一个节点添加到队列中。这种情况下就需要用到条件变量。

条件变量函数:

1.初始化

函数原型:int   pthread_cond_init(pthread_cond_t   *restrict   cond,const   pthread_condattr_t   *restrict   attr);

参数:

            cond:要初始化的条件变量。

            attr:NULL

2.销毁

函数原型:int   pthread_cond_destroy(pthread_cond_t   *cond);

3.等待条件满足

函数原型:int  pthread_cond_wait(pthread_cond_t   *restrict   cond,pthread_mutex_t   *restrict   mutex);

参数:

            cond:要在某个条件变量上等待。

            mutex:互斥量。

注:一个线程在某个条件变量上等待同时必须释放锁。因为若不释放锁直接等待,那么其他线程无法获得锁,从而进行操作。那么那个线程相当于在抱着锁死等,从而造成死锁现象。

4.唤醒等待

int   pthread_cond_broadcast(pthread_cond_t   *cond);          (唤醒一群线程)

int   pthread_cond_signal(pthread_cond_t   *cond);          (唤醒单个线程)

注:当线程被唤醒时,要重新从等待处获得锁,并且继续执行。


代码实例:

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

pthread_cond_t cond;
pthread_mutex_t mutex;

void *r1(void *arg){
    while(1){
        pthread_cond_wait(&cond,&mutex);
        printf("活动\n");
    }
}

void *r2(void *arg){
    while(1){
        pthread_cond_signal(&cond);
        sleep(1);
    }
}

int main(){
    pthread_t t1,t2;

    pthread_cond_init(&cond,NULL);
    pthread_mutex_init(&mutex,NULL);

    pthread_create(&t1,NULL,r1,NULL);
    pthread_create(&t2,NULL,r2,NULL);

    pthread_join(t1,NULL);
    pthread_join(t2,NULL);

    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);
}
结果演示:


#include <stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<pthread.h>
#include<sys/syscall.h>

pthread_mutex_t lock;
pthread_cond_t cond;

typedef struct _Node{
    int data;
    struct _Node *next;
}node_t,*node_p,**node_pp;

node_p alloc_node(int x){
    node_p p = (node_p)malloc(sizeof(node_t));
    if(!p){
        perror("malloc");
        exit(1);
    }
    p->data = x;
    p->next = NULL;
    return p;
}

void list_push(node_p h,int x){
    node_p p  = alloc_node(x);
    p->next = h->next;
    h->next = p;
}

int is_empty(node_p h){
    return h->next==NULL?1:0;
}

void list_pop(node_p head,int* x){
    if(!is_empty(head)){
    node_p p = head->next;
    *x = p->data;
    head->next = p->next;
    free(p);
    p = NULL;
    }
}

void init_list(node_pp h){
    *h = alloc_node(0);
}

void list_destroy(node_p h){
    int x;
    while(!is_empty(h)){
        list_pop(h,&x);
    }
    free(h);
}

void show_list(node_p h){
    if(!is_empty(h)){
        node_p p = h->next;
        while(p){
            printf("%d ",p->data);
            p = p->next;
        }
        printf("\n");
    }
}

void *consumer(void *arg){
    node_p head = (node_p)arg;
    while(1){
        int data;
        pthread_mutex_lock(&lock);
        if(is_empty(head)){
        printf("no data for consumer!\n");
        pthread_cond_wait(&cond,&lock);
        }
        list_pop(head,&data);                                                                                                                                                            
        printf("consumer done! data is%d\n",data);
        pthread_mutex_unlock(&lock);
    }
}

void *product(void* arg){
    int data = 0;
    node_p head = (node_p)arg;
    while(1){
        data = rand()%100+1;
        pthread_mutex_lock(&lock);
        list_push(head,data);
        pthread_mutex_unlock(&lock);
        pthread_cond_signal(&cond);

        printf("product done!data is:%d\n",data);
        sleep(5);
    }
}

int main(){
    node_p head;
    init_list(&head);
    pthread_mutex_init(&lock,NULL);
    pthread_cond_init(&cond,NULL);

    srand((unsigned long)time(NULL));

    pthread_t c,p;
    pthread_create(&c,NULL,consumer,(void*)head);
    pthread_create(&p,NULL,product,(void*)head);

    pthread_join(c,NULL);
    pthread_join(p,NULL);

    
    list_destroy(head);
    pthread_mutex_destroy(&lock);
    pthread_cond_destroy(&cond);
}
结果演示:


总结:条件变量用于实现线程间的同步。

  • 死锁问题

1.产生死锁的主要原因:

    (1)系统资源不足;

    (2)进程运行推进的顺序不合适;

    (3)资源分配不当。

2.死锁产生的四个必要条件:

    (1)互斥条件:进程要求对所分配的资源进行排他性控制,即在一段时间内,某资源仅为一个进程所占有。此时若有其它进程请求该资源,则请求进程只能等待。

    (2)不剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走。即只能由获得该资源的进程自己释放(只能是主动释放)。

    (3)请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时要求进程被阻塞,但对自己已获得的资源保持不放。

    (4)循环等待条件:存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被链中的下一个进程所请求。即存在一个处于等待状态的进程集合(p1,p2,......,pn),其中pi等待的资源被p(i+1)占有,其中i=0,1,......,n-1。pn等待的资源被p0占有,如下图所示:

                               


3.避免死锁的方法:

    (1)加锁顺序(线程按照一定的顺序加锁)。

    (2)加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)。

    (3)死锁检测。

  • 生产者消费者模型

    

三种关系

    (1)生产者与生产者之间:互斥

    (2)生产者与消费者之间:互斥且同步

    (3)消费者与消费者之间:互斥

两种角色(通常由进程提供):生产者、消费者

一个交易场所:临界区



阅读更多
个人分类: 知识点小结
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

不良信息举报

线程同步与互斥

最多只允许输入30个字

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭