linux学习之旅(23)----线程同步

线程同步

在多个线程同时访问一个资源时,在一个时间段只允许有一个线程占用资源,其他进程在这期间只能等待,就被成为线程同步。即一个进程在操作资源时,其他线程不允许对资源进行操作。

程序:将一个数从0加到10000,使用两个线程实现,每个线程分别加5000

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

int count=0;
void* do_pthread(void* argc)
{
    int i=0,val;
    for(i=0;i<5000;++i)
    {
        val=count;
        //printf("%x,%d\n",(unsigned int)pthread_self(),val+1);
        //usleep(10);
        count=val+1;
    }
    printf("%x,%d\n",(unsigned int)pthread_self(),val+1);
    pthread_detach(pthread_self());
    pthread_exit((void*)1);
}

int main(int argc,char* argv[])
{
    pthread_t tid[2];
    pthread_create(&tid[0],NULL,do_pthread,NULL);
    pthread_create(&tid[1],NULL,do_pthread,NULL);
    pthread_exit((void*)1);
}

(1)注释代码11和12行。

结果正确为10000。

(2)注释12行,解开11的注释

结果错误

(3)注释11行,解开12行注释,

分析程序:

printf("%x,%d\n",(unsigned int)pthread_self(),val+1);//代码11行,答应结果
usleep(10);//代码12行,程序睡眠10微秒

这两句代码并没有对数据进程操作,那为什么会导致结果的差异那?

原因:加法操作不是一个原子操作,即加法操作的汇编指令不为一条。通过反汇编可以得到加法的汇编指令。

mov 操作数1的地址 %eax //将操作数放到eax寄存器中
add 操作数2 %eax      //将操作数2和寄存器中的操作1相加,并放入eax寄存器中
mov %eax 和的地址     //将结果从eax寄存器中,放回结果中

 

在一个线程中这是不存在问题的。但如果在多个线程中就会存在一定的问题。

借助上图我们分析以上3个结果:

结果1:注释11和12行后结果正常

原因:两个线程不是同时创建处来的,而且5000次加法对于现代计算机的运算时间太短了,可能第二个线程还没有创建出来,这5000次加法就执行完成了,这样线程2就会在线程1的结果继续加。这样两个线程相互间不影响。就不会对结果产出影响。我们将5000次加法修改为5千万次,并在第一线程创建完,让线程1睡眠10微秒,等待线程2,观察结果:

结果是错误的。

结果2和结果3:放开11行或12行的注释结果错误

原因:和上图中分析相同,在一个线程使用共享资源时,另一个线程也使用了共享资源。一般就会像结果2一样,极端就会出现结果3的情况。

那要如果修改程序可以使的结果正常那?

(1)将加法修改为原子操作(除非你是系统内核的设计者,否则没有办法做到这一点)

(2)在一个线程对变量操作时,使得其他线程不能对当前变量进行操作(线程同步)。

为什么要进行线程同步?

(1)共享资源,无论哪个线程对可以对共享资源进行操作。

(2)对共享变量的操作不是原子操作。

(3)线程对变量的操作的顺序不是确定的(由于原因2导致)

线程同步的方法:(1)互斥锁、(2)信号量、(3)条件变量

互斥锁(mutex)

int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *mutrexattr);//初始化互斥锁
int pthread_mutex_lock(pthread_mutex_t *mutex);//锁定互斥锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);//预锁定互斥锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);//解锁互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);//销毁互斥锁

需要注意的是:互斥锁保护的区域为加锁和解锁之间的代码,即执行pthread_mutex_lock之后和pthread_mutex_unlock之前的区域为保护区域。当一个线程执行到pthread_mutex_lock处时,如果该锁此时被并一个锁使用,那么当前线程就会被阻塞,等待另一个线程释放互斥锁(互锁一旦加上,就只能有加锁的线程解锁,一旦该线程结束时没有解锁,那么该锁就不能被解开)。在互斥锁使用完毕,需要调用pthread_mutex_destroy()函数进行释放。

程序:修改上例程序

//静态初始化互斥锁
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
//动态初始化就时使用pthread_mutex_init()函数
void* do_pthread(void* argc)
{
    int i=0,val;
    for(i=0;i<50000000;++i)
    {
        pthread_mutex_lock(&mutex);
        val=count;
        //printf("%x,%d\n",(unsigned int)pthread_self(),val+1);
        //usleep(10);
        count=val+1;
        pthread_mutex_unlock(&mutex);
    }
    printf("%x,%d\n",(unsigned int)pthread_self(),val+1);
    pthread_detach(pthread_self());
    pthread_exit((void*)1);
}

这样结果会变的正确(没有释放锁资源)。

程序3:两个进程一个对变量++,一个对变量--。

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

pthread_mutex_t mutex;
int count=0;
int run=1;

void* addPthread(void* argc)
{
    while(run)
    {
        //pthread_mutex_trylock(&mutex);
        pthread_mutex_lock(&mutex);
        ++count;
        printf("ADD:%d\n",count);
        pthread_mutex_unlock(&mutex);
        usleep(1);
    }
    pthread_exit(argc);
}

void* delPthread(void* argc)
{
   while(run)
    {
        pthread_mutex_lock(&mutex);
        --count;
        printf("DEL:%d\n",count);
        pthread_mutex_unlock(&mutex);
        usleep(1);
    }
    pthread_exit(argc);
}

int main()
{
    pthread_t tid1,tid2; 
    //动态初始化互斥锁
    pthread_mutex_init(&mutex,NULL);
    pthread_create(&tid1,NULL,addPthread,NULL);
    pthread_create(&tid2,NULL,delPthread,NULL);
    sleep(1);
    run=0;
    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);
    //销毁互斥锁
    pthread_mutex_destroy(&mutex);
    return 0;
}

如果不让线程睡眠,就可能会导致另一个线程将事件全部消耗完的情况。

信号量:

线程的信号量与进程的信号量类似,使用线程的信量可以高效的完成基于线程的资源计数。信号量实际上是一个非负的整数计数器,用来实现对公共资源的控制,在公共资源增加时,信号量增加;在公共资源减少时,信号量减少。只有信号量大于0时,才能访问信号量所表示的公共资源。

int sem_init(sem_t *sem,int pshared,unsigned int value);//线程信号量初始化函数

函数功能:

初始化一个信号量

参数说明:

*sem:信号量类型的变量。

pshared:表示信号量的共享类型,当值为0时,信号量在当前进程的多个线程之间共享。当值不为0时,信号量可以在进程之间共享。value:用于设置信号量初始化时的信号量的值。

int sem_post(sem_t *sem);//线程信号量增加函数

功能:增加信号量的值,每次增加的值为1,当有线程等待这个信号时,等待的线程将返回。

int sem_wait(sem_t *sem);//线程信号量等待函数 

功能:减少信号量的值,如果信号量的值为0,则线程会一直阻塞到信号量的值大于0为止。

int sem_destroy(sem_t *sem);//释放信号量

条件变量:

和互斥量不同,条件变量是用等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊情况发生为止,通常条件量和互斥量同时使用。

pthread_cond_t      //类型
int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *condarrt);
//等待条件成立,释放锁,同时阻塞等待条件变量为真
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
int pthread_cond_timedwait(pthread_cond_t *cond,pthread_mutex *mutex const timespec *abstime);//设置等待事件,如果事件到了,任然没有激活,会返回ETIMEOUT
//激活条件变量
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);//激活所有等待线程
//清楚条件变量。无线程等待,否则返回EBUSY
int pthread_cond_destroy(pthread_cond_t *cond);

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值