线程同步中对同步的理解
对于多线程程序来说,同步(synchronization)是指在一定的时间内只允许某一个线程访问某个资源。而在此时间内,不允许其它的线程访问该资源。
“同”字从字面上容易理解为一起动作其实不是,“同”字应是指协同、协助、互相配合。如进程、线程同步,可理解为进程或线程A和B一块配合,A执行到一定程度时要依靠B的某个结果,于是停下来,示意B运行;B依言执行,再将结果给A;A再继续操作。
同步就是协同步调,按预定的先后次序进行运行。如:你说完,我再说。
所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回,同时其它线程也不能调用这个方法。按照这个定义,其实绝大多数函数都是同步调用(例如sin, isdigit等)。但是一般而言,我们在说同步、异步的时候,特指那些需要其他部件协作或者需要一定时间完成的任务。例如Window API函数SendMessage。该函数发送一个消息给某个窗口,在对方处理完消息之前,这个函数不返回。当对方处理完毕以后,该函数才把消息处理函数所返回的LRESULT值返回给调用者。
在多线程编程里面,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性。
多线程访问同一个变量的问题
//============================================================================
// Name : synProblem.cpp
// Author : Ryan
// Version :
// Copyright : zjut
// Description : 线程同步的必要性
//============================================================================
#include <iostream>
#include <pthread.h>
using namespace std;
int i=0;
void* thread1(void* arg)
{
static int count=10000000;
while(count--)
{
i++;//i++;i++;i++;i++;i++;
}
return (void*)0;
}
void* thread2(void* arg)
{
static int count=10000000;
while(count--)
{
i++;//i++;i++;i++;i++;i++;
}
return (void*)0;
}
int main(void) {
pthread_t ntid1,ntid2;
if(pthread_create(&ntid1, NULL, thread1, NULL))
return 0;
if(pthread_create(&ntid2, NULL, thread2, NULL))
return 0;
cout<<"thread created"<<endl;
// thread_j
int *state;
//sleep(1);
pthread_join(ntid1, (void**)(&state));
pthread_join(ntid2, (void**)(&state));
cout<<"i:"<<i<<endl;
return 0;
}
上个代码我们想要两个进程一起对i加一10000000次,预期结果为20000000。但是实际运行代码发现,i的值有可能小于20000000。当然有时也是正确的。
上诉现象的原因:cpu对放在内存中的数据读写是需要消耗时间的,在一个线程对变量作修改后还没来得及将更新值存入内存中,另一个线程就已经读入了前一个线程没有更新的变量值,导致程序可能出现未知的BUG。下图可以比较清楚的表示这个问题。
同步模型
我们可以通过互斥锁(mutex),条件变量(condition variable)和读写锁(reader-writer lock) 和信号来同步资源。
■互斥锁仅允许每次使用一个线程来执行特定的部分代码或者访问特定数据。
■读写锁允许对受保护的共享资源进行并发读取和独占写入。要修改资源,线程必须首先
获取互斥写锁。只有释放所有的读锁之后,才允许使用互斥写锁。
■条件变量会一直阻塞线程,直到特定的条件为真。
■计数信号量通常用来协调对资源的访问。使用计数,可以限制访问某个信号的线程数
量。达到指定的计数时,信号将阻塞。
互斥锁
互斥锁的初始化和销毁
为了让线程访问数据不产生冲突,这要就需要对变量加锁,使得同一时刻只有一个线程可以访问变量。互斥量本质就是锁,访问共享资源前对互斥量加锁,访问完成后解锁
当互斥量加锁以后,其他所有需要访问该互斥量的线程都将阻塞
当互斥量解锁以后,所有因为这个互斥量阻塞的线程都将变为就绪态,第一个获得cpu的线程会获得互斥量,变为运行态,而其他线程会继续变为阻塞,在这种方式下访问互斥量每次只有一个线程能向前执行
互斥量用pthread_mutex_t类型的数据表示,在使用之前需要对互斥量初始化
1、如果是动态分配的互斥量,可以调用pthread_mutex_init()函数初始化
2、如果是静态分配的互斥量,还可以把它置为常量PTHREAD_MUTEX_INITIALIZER
3、动态分配的互斥量在释放内存之前需要调用pthread_mutex_destroy()
int pthread_mutex_init(pthread_mutex_t*restrict mutex,
const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(pthread_mutex_t*mutex);
pthread_mutex_t mutex =PTHREAD_MUTEX_INITIALIZER;
加锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
成功返回0,失败返回错误码。如果互斥量已经被锁住,那么会导致该线程阻塞
int pthread_mutex_trylock(pthread_mutex_t *mutex);
成功返回0,失败返回错误码。如果互斥量已经被锁住,不会导致线程阻塞
解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
成功返回0,失败返回错误码。如果一个互斥量没有被锁住,那么解锁就会出错
死锁:
设计互斥量时有时会出现两个线程互锁,导致线程死锁的情况。
出现这种问题的情况如下图所示。
线程1解除阻塞的条件:线程2将mutex 1解锁
线程2解除阻塞的条件:线程1将mutex 2解锁
线程1: lock mutex 1成功同时 线程2 : lock mutex 2成功
线程1: lock mutex 2失败,阻塞
线程2: lock mutex 1失败,阻塞
示例代码:
/*============================================================================
// Name : synProblem.cpp
// Author : Ryan
// Version :
// Copyright : zjut
// Description : 线程同步的必要性
加上互斥锁
//============================================================================*/
#include <iostream>
#include <pthread.h>
using namespace std;
int i=0;
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;/*互斥锁*/
void* thread1(void* arg)
{
static int count=10000000;
pthread_mutex_lock(&mutex);/*上锁*/
while(count--)
{
i++;//i++;i++;i++;i++;i++;
}
pthread_mutex_unlock(&mutex);/*解锁*/
return (void*)0;
}
void* thread2(void* arg)
{
static int count=10000000;
pthread_mutex_lock(&mutex);/*上锁*/
while(count--)
{
i++;//i++;i++;i++;i++;i++;
}
pthread_mutex_unlock(&mutex);/*解锁*/
return (void*)0;
}
int main(void) {
pthread_t ntid1,ntid2;
if(pthread_create(&ntid1, NULL, thread1, NULL))
return 0;
if(pthread_create(&ntid2, NULL, thread2, NULL))
return 0;
cout<<"thread created"<<endl;
// thread_j
int *state;
//sleep(1);
pthread_join(ntid1, (void**)(&state));
pthread_join(ntid2, (void**)(&state));
cout<<"i:"<<i<<endl;
pthread_mutex_destroy(&mutex);
return 0;
}