线程同步与互斥

临界资源:

一个进程的资源对于运行在它内部的线程是共享的,一次只允许一个线程使用的资源叫做临界资源

临界区:

访问临界资源的那段程序叫做临界区

线程的同步:

同步就是协同步调,按照预定的先后顺序执行。

“同”字应是指协同、协助、互相配合。

线程的互斥:

某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。

但互斥无法限制访问者对资源的访问顺序,即访问是无序的。


同步的方法(互斥量、条件变量、信号量)


互斥量(mutex)

多个线程同时访问共享数据时,经常会发生冲突,因此常常引入互斥锁来解决这个问题。

冲突:例如对一个全局变量g_var进行加一操作,这样的操作需要三条指令来完成:(1、把该变量的值从内存读到寄存器;2、寄存器进行加一操作;3、把修改后的值写回内存),因为这三条指令的执行不是原子操作,有可能线程1执行了第一步(把变量值读到寄存器),此时内核调度线程2,线程2也把该值读到寄存器,它们都进行加一操作后写回内存,导致最后结果只加了一次,而我们希望是两次。

解决:引入互斥锁,获得锁的线程可以对变量进行“读-修改-写”操作,这个操作是原子的,完成这个操作后释放锁给别的线程,没有获得锁的线程只能等待而不能访问该共享数据。

有关函数:

1.互斥锁的初始化及销毁

其中初始化既可以使用函数pthread_mutex_init(),也可以定义pthread_mutex_t类型的变量mutex,并赋值PTHREAD_MUTEX_INITIALIZER

2.对mutex的加锁及解锁

一个线程可以调用pthread_mutex_lock获得Mutex,如果这时另一个线程已经调用pthread_mutex_lock获得了该Mutex,则当前线程需要挂起等待,直到另一个线程调用pthread_mutex_unlock释放Mutex,当前线程被唤醒,才能获得该Mutex并继续执行。

如果一个线程既想获得锁,又不想挂起等待,可以调用pthread_mutex_trylock,如果Mutex已经被另一个线程获得,这个函数会失败返回EBUSY,而不会使线程挂起等待。


Mutex的lock和unlock的伪码(以x86的xchg指令为例):

不使用互斥量代码:

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

void *thread(void *arg)
{
    int count=2000;
    while(count-- >0){
        int var=g_var;
        printf("this is thread%d,g_var=%d\n",(int)arg,g_var);
        g_var=var+1;
    }
}

int main()
{
    pthread_t tid1,tid2;
    
    if(pthread_create(&tid1,NULL,thread,(void*)1)!=0){
        printf("creat tid1 is failed\n");
    }
    if(pthread_create(&tid1,NULL,thread,(void*)2)!=0){
        printf("creat tid1 is failed\n");
    }
    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);
    
    return 0;
}

运行结果:

第一次运行线程1和线程2一共加了2275次

第二次运行一共把g_var加了2000次

使用互斥量实现代码:

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

//pthread_mutex_t mutex_lock=PTHREAD_MUTEX_INITIALIZER;

struct info
{
    int _var;
    pthread_mutex_t  *_mutex_lock;
}info;

int g_var=0;
void *thread(void *arg)
{
    struct info* _lock=(struct info*)arg;
    int count=2000;
    while(count-- >0){
        pthread_mutex_lock(_lock->_mutex_lock);
        int var=g_var;
        printf("this is thread%d,g_var=%d\n",_lock->_var,g_var);
        g_var=var+1;
        pthread_mutex_unlock(_lock->_mutex_lock);
    }   
}

int main()
{
    pthread_t tid1,tid2;
    pthread_mutex_t mutex_lock;
    int err=pthread_mutex_init(&mutex_lock,NULL);
    if(err!=0){
        printf("%s\n",strerror(err));
    }

    struct info info1;
    info1._var=1;
    info1._mutex_lock=&mutex_lock;

    if(pthread_create(&tid1,NULL,thread,&info1)!=0){
        printf("creat tid1 is failed\n");
    }

    struct info info2;
    info2._var=2;
    info2._mutex_lock=&mutex_lock;

    if(pthread_create(&tid2,NULL,thread,&info2)!=0){
        printf("creat tid2 is failed\n");
    }
    
    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);

    err=pthread_mutex_destroy(&mutex_lock);
    if(err!=0){
        printf("%s\n",strerror(err));
    }

    return 0;
}

运行结果:

线程1和线程2共执行了4000次

总结:当一个线程已经获得锁,另一个线程需挂起等待,当该线程释放锁后,会唤醒正在等待线程。每个mutex都有一个等待队列,当线程挂起时,就加入该等待队列,状态设置为睡眠。一个线程要唤醒别的线程,只需要从等待队列中取出一项加入就绪队列并把它的状态设置为就绪。

有的时候一个线程会两次获得锁,第一次正常获得,第二次获得所得时候发现锁已经被别占用,它就会挂起,导致自己占用了锁却处于挂起状态,这样就产生了死锁。

死锁:

产生死锁的原因

1、进程资源不足

2、进程推进的顺序不合适

3、系统资源的分配不当

产生死锁的必要条件:

1、互斥条件:一个资源一次只能被一个进程使用

2、请求与保持条件:一个已经进程因请求资源而阻塞,对已获得的资源保持不放

3、不剥夺条件:进程已获得的资源,在没使用完之前,不能强行剥夺

4、循环等待条件:若干个进程之间形成一种头尾相接的循环等待资源关系

产生死锁时,以上四个必要条件一定都成立。


条件变量

生产者消费者模型:

关系:

     同步

生产者<—————>消费者   

互斥

互斥

生产者<—————>生产者


      互斥

消费者<—————>消费者  

场所  

缓冲区,下文以链表方式实现

1.单个生产者,单个消费者,且生产者和消费者访问链表的顺序是LIFO的

代码实现:

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

pthread_mutex_t _mutex_lock=PTHREAD_MUTEX_INITIALIZER;

pthread_cond_t need_product=PTHREAD_COND_INITIALIZER;

typedef struct List List;
struct List
{
    int _var;
    List *_next;
}*head=NULL;
//head=NULL;

void* product(void *arg)
{
    while(1){
        pthread_mutex_lock(&_mutex_lock);
        List* p=(List*)malloc(sizeof(List));
        p->_var=(rand()%2000);
        p->_next=head;
        head=p;
        printf("call consumer! product success,val is :%d\n",p->_var);
        pthread_mutex_unlock(&_mutex_lock);
        sleep(rand()%3);
        pthread_cond_signal(&need_product);
    }
}

void* consumer(void *arg)
{
    while(1){
        pthread_mutex_lock(&_mutex_lock);
        if(head==NULL){
            pthread_cond_wait(&need_product,&_mutex_lock);
        }
        List *p=head;
        head=head->_next;
        p->_next=NULL;
        pthread_mutex_unlock(&_mutex_lock);
        printf("consumer has get protect:%d\n",p->_var);
        free((void*)p);
        p=NULL;
    }
}

int main()
{
    int err;
    pthread_t p;
    pthread_t c;

    err=pthread_create(&p,NULL,product,NULL);
    if(err!=0){
        printf("%s\n",strerror(err));
    }

    err=pthread_create(&c,NULL,consumer,NULL);
    if(err!=0){
        printf("%s\n",strerror(err));
    }

    void *status;
    pthread_join(p,&status);
     printf("%s\n",status);
    pthread_join(c,&status);
    printf("%s\n",status);

    pthread_cond_destroy(&need_product);

    pthread_mutex_destroy(&_mutex_lock);

    return 0;
}

运行结果:

==================================================================

2.单个生产者,单个消费者,且生产者和消费者访问链表的顺序是FIFO的

代码实现:

fifo_cond.c

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

pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;
//pthread_cond_t need_product=PTHREAD_COND_INITIALIZER;
pthread_cond_t need_product;

typedef int datatype;

typedef struct node{
    datatype _val;
    struct node *_next;
}node,*node_p,**node_pp;

typedef struct list_info
{
    node_p _head;
    node_p _tail;
}list_info,*list_info_p;

void init_list(node_pp head,node_pp last)
{
    *head=NULL;
    *last=*head;
}

node_p buy_node(datatype data)
{
    node_p p=(node_p)malloc(sizeof(node));
    p->_val=data;
    p->_next=NULL;

    if(p==NULL){
        return NULL;
    }
    return p;
}

int push(node_pp list,node_pp last,datatype data)
{
    node_p p=buy_node(data);
    if(p==NULL){
        return -1;
    }
    if(*last==NULL){
        *last=p;
        *list=p;
    }
    else{
        (*last)->_next=p;
        (*last)=(*last)->_next;
    }
    return data;
}

int destroy_node(node_pp list,node_pp last)
{
    if((*list)!=NULL){
        node_p tmp=*list;
        *list=(*list)->_next;
        int i=tmp->_val;
        free(tmp);
        return i;
    }
    *last=NULL;
     return -1;
}

int pop(node_pp list,node_pp last)
{
    return destroy_node(list,last);
}

int show_list(node_p list,node_p last)
{
    while(list!=last){
        printf("%d->",list->_val);
        list=list->_next;
    }
    if(last!=NULL)
        printf("%d\n",last->_val);
}

void *product(void *arg)
{
    while(1){
        datatype data=rand()%100;
        pthread_mutex_lock(&lock);
        push(&(((list_info_p)arg)->_head),&(((list_info_p)arg)->_tail),data);
        printf("Product success,val:%d\n",data);
        pthread_mutex_unlock(&lock);
        sleep(2);
        pthread_cond_signal(&need_product);
    }
}

void *consumer(void *arg)
{
    while(1){
        int ret=-1;
//      sleep(4);
        pthread_mutex_lock(&lock);
        while(-1==(ret=pop(&(((list_info_p)arg)->_head),&(((list_info_p)arg)->_tail)))){
            pthread_cond_wait(&need_product,&lock);
        }
        pthread_mutex_unlock(&lock);
        printf("consumer success,val:%d\n",ret);
     }
}

int main()
{
    pthread_cond_init(&need_product,NULL);

    node_p head,tail;
    init_list(&head,&tail);

    list_info _list_info;
    _list_info._head=head;
    _list_info._tail=tail;

    pthread_t tid1,tid2;
    pthread_create(&tid1,NULL,product,(void*)(&_list_info));
    pthread_create(&tid2,NULL,consumer,(void*)(&_list_info));

    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);
    
    pthread_cond_destroy(&need_product);
    pthread_mutex_destroy(&lock);

//  int data=0;
//  while(data<10){
//      push(&head,&last,data);
//      show_list(head,last);
//      data++;
//  }
//
//  while(data>0){
//      pop(&head,&last);
//      show_list(head,last);
//      data--;
//  }
    return 0;
}

运行结果:

consumer()有sleep(4);

运行结果:

consumer()函数中没有sleep(4)这条语句

从以上两次结果可以看出消费者是按找生产产品的顺序来消费的,如果生产者生产的慢,消费者会等待

==================================================================

3.多个生产者,多个消费者


实现代码:

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

pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t need_product=PTHREAD_COND_INITIALIZER;
pthread_cond_t need_product;

typedef int datatype;

typedef struct node{
    datatype _val;
    struct node *_next;
}node,*node_p,**node_pp;

node_p head,tail;

typedef struct list_info
{
    node_p _head;
    node_p _tail;

    int _flag;
}list_info,*list_info_p;

void init_list(node_pp head,node_pp last)
{
    *head=NULL;
    *last=*head;
}

node_p buy_node(datatype data)
{
    node_p p=(node_p)malloc(sizeof(node));
    p->_val=data;
    p->_next=NULL;

    if(p==NULL){
        return NULL;
    }
    return p;
}

int push(node_pp list,node_pp last,datatype data)
{
    node_p p=buy_node(data);
    if(p==NULL){
        return -1;
    }
    if(*last==NULL){
        *last=p;
         *list=p;
    }
    else{
        (*last)->_next=p;
        (*last)=(*last)->_next;
    }
    return data;
}

int destroy_node(node_pp list,node_pp last)
{
    if((*list)!=NULL){
        node_p tmp=*list;
        *list=(*list)->_next;
        int i=tmp->_val;
        free(tmp);
        return i;
    }
    *last=NULL;
    return -1;
}

int pop(node_pp list,node_pp last)
{
    return destroy_node(list,last);
}

int show_list(node_p list,node_p last)
{
    while(list!=last){
        printf("%d->",list->_val);
        list=list->_next;
    }
    if(last!=NULL)
        printf("%d\n",last->_val);
}

void *product(void *arg)
{
    while(1){
        datatype data=rand()%100;
        pthread_mutex_lock(&lock);
        push(&head,&tail,data);
        printf("Product%d put success,val:%d\n",(int)arg,data);
        pthread_mutex_unlock(&lock);
        sleep(1);
//      pthread_cond_signal(&need_product);
        pthread_cond_broadcast(&need_product);
    }
}

void *consumer(void *arg)
{
     while(1){
         int ret=-1;
         sleep(2);
         pthread_mutex_lock(&lock);
         while(-1==(ret=pop(&head,&tail))){
             pthread_cond_wait(&need_product,&lock);
         }
         pthread_mutex_unlock(&lock);
         sleep(1);
         printf("consumer%d take success,val:%d\n",(int)arg,ret);
     }
}

int main()
{
     init_list(&head,&tail);

       pthread_t tid1,tid2;
       pthread_create(&tid1,NULL,product,(void*)1);
       pthread_create(&tid2,NULL,product,(void*)2);

       pthread_t tid3,tid4,tid5;
       pthread_create(&tid3,NULL,consumer,(void*)3);
       pthread_create(&tid4,NULL,consumer,(void*)4);
       pthread_create(&tid5,NULL,consumer,(void*)5);

       pthread_join(tid1,NULL);
       pthread_join(tid2,NULL);

       pthread_join(tid3,NULL);
       pthread_join(tid4,NULL);
       pthread_join(tid5,NULL);

       pthread_cond_destroy(&need_product);
       pthread_mutex_destroy(&lock);

     return 0;
}

运行结果:


以上有2个生产者,3个消费者  生产者生产出的数据放入同一链表中,消费者也都从该链表取数据,任何一刻对象对改链表进行操作时,别的对象都不能对该链表进行操作,实现了互斥功能。

时间: 2024-08-11 05:43:08

线程同步与互斥的相关文章

Android多线程研究(3)——线程同步和互斥及死锁

为什么会有线程同步的概念呢?为什么要同步?什么是线程同步?先看一段代码: package com.maso.test; public class ThreadTest2 implements Runnable{ private TestObj testObj = new TestObj(); public static void main(String[] args) { ThreadTest2 tt = new ThreadTest2(); Thread t1 = new Thread(tt,

一起talk C栗子吧(第一百一十六回:C语言实例--线程同步之互斥量二)

各位看官们,大家好,上一回中咱们说的是线程同步之信号量的例子,这一回咱们继续说该例子.闲话休提,言归正转.让我们一起talk C栗子吧! 我们在上一回中详细介绍了互斥量相关函数的用法,这一回中,我们介绍如何使用这些函数来操作互斥量. 下面是详细的操作步骤: 1.定义一个互斥量A,用来同步线程: 2.在创建线程的进程中使用pthread_mutex_init函数初始化互斥量,互斥量的属性使用默认值: 3.在读取数据的线程中读取数据,首先使用pthread_mutex_lock函数对互斥量A进行加锁

线程同步与互斥之条件·变量

条件变量(condition variable) 线程间的同步与互斥技术,主要以互斥锁和条件变量为主,条件变量和互斥所的配合使用可以很好的处理对于条件等待的线程间的同步问题.举个例子:消费者和生产者问题. 消费者与生产者最基本的关系是服务与被服务的关系,但是在线程同步与互斥中强调的是两者访问资源的关系.首先生产与消费的关系为:同步与互斥,生产与生产的关系为:互斥,消费与消费的关系为:互斥.所以维护这三种关系的有两类人:生产者与消费者.并且生产数据与消费数据必须有场所. 所以将其简述为三种关系两类

exec函数族,守护进程,线程同步和互斥

2015.3.2 进程和程序有三点不同:1,存在位置不同,程序:硬盘,磁盘.进程:内存2. 程序是静态的,进程是动态的 执行./a.out -->bash->bash程序调用fork()-->子进程将./a.out后面的参数存放到argv[].然后调用exec处理这些参数,最后子进程退出,光标闪动 进程进入停止态:1,调试的时候,2,将前台变成后台运行 线程:每个程序加载到内存后可以对应创建一个或多个顺序执行流(能使进程在同一时刻做不止一件事,每个线程处理各自独立的任务) 回调函数 同步

Linux系统编程——线程同步与互斥:无名信号量

信号量概述 信号量广泛用于进程或线程间的同步和互斥,信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问. 编程时可根据操作信号量值的结果判断是否对公共资源具有访问的权限,当信号量值大于 0 时,则可以访问,否则将阻塞.PV 原语是对信号量的操作,一次 P 操作使信号量减1,一次 V 操作使信号量加1. 信号量主要用于进程或线程间的同步和互斥这两种典型情况. 信号量用于互斥: 信号量用于同步: 在 POSIX 标准中,信号量分两种,一种是无名信号量,一种是有名信号量.无名信号量一般用

C#学习笔记---线程同步:互斥量、信号量、读写锁、条件变量

http://www.cnblogs.com/maxupeng/archive/2011/07/21/2112282.html 一.互斥量(mutex) 互斥量本质上是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁. 对互斥量进行加锁以后,任何其它试图再次对互斥量加锁的线程将会被阻塞直到当前线程释放该互斥锁.如果释放互斥锁时有多个线程阻塞,所有在该互斥锁上的阻塞线程都会变成可运行状态,第一个变为运行状态的线程可以对互斥量加锁,其它线程将会看到互斥锁依然被锁住,只能回去再次

线程同步之——互斥量及死锁问题

互斥量:多个线程同时访问共享数据时可能会冲突,这跟信号的可重性是同样的问题.如 果两个线程都要把某个全局变量增加1,这个操作在某平台需要三条指令完成: 1. 从内存读变量值到寄存器 2. 寄存器的值加1 3. 将寄存器的值写回内存 先举个例子:创建两个线程,各把counter增加5000次,正常情况下最后counter应该等于10000. 代码实现如下: 结果: 可以看到,每次运行程序的结果都不一样.说明在调用过程中发生了互斥现象. 解决办法:加互斥锁 实现多线程同步可以引互斥锁(Mutex,M

线程同步与互斥量

线程同步: 当多个线程共享相同的内存时,需要确保每个线程看到一致的数据视图.如果每个线程使用的变量都是其他线程不会读取和修改的,那么就不存在一致性问题.同样,如果变量是只读的,多个线程同时读取该变量也不会有一致性的问题.但是,当一个线程可以修改的变量,其他线程也可以读取和修改的时候,我们就需要对这些线程进行同步,确保它们在访问变量的存储内容时不会访问到无效的值. 为了解决这个问题,线程不得不使用锁,同一时间只允许一个线程访问该变量. 两个或多个线程在同一时间修改同一变量,也需要进行同步.考虑变量

1线程同步:互斥量,死锁

 1线程为什么要同步 A:共享资源,多个线程都可对共享资源操作. B:线程操作共享资源的先后顺序不确定. C:处理器对存储器的操作一般不是原子操作. 2互斥量 mutex操作原语 pthread_mutex_t pthread_mutex_init pthread_mutex_destroy pthread_mutex_lock pthread_mutex_trylock pthread_mutex_unlock 3 临界区(Critical Section) 保证在某一时刻只有一个线程能访

APUE学习笔记——11 线程同步、互斥锁、自旋锁、条件变量

线程同步 同属于一个进程的不同线程是共享内存的,因而在执行过程中需要考虑数据的一致性. 假设:进程有一变量i=0,线程A执行i++,线程B执行i++,那么最终i的取值是多少呢?似乎一定是i=2:其实不然,如果没有考虑线程同步,i的取值可能是1.我们先考虑自加操作的过程:a,首先将内存中i的值copy到寄存器:b,对寄存器中i的copy进行自加:c,将寄存器中自加的结果返回到内存中.回到例子,如果线程A执行完abc三个步骤,线程B在执行者三个步骤,那么结果就应该为2.但是自加不是原子操作,假如执行