线程同步
在多个线程同时访问一个资源时,在一个时间段只允许有一个线程占用资源,其他进程在这期间只能等待,就被成为线程同步。即一个进程在操作资源时,其他线程不允许对资源进行操作。
程序:将一个数从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);