# 一、 引起同步互斥问题的原因

同步:因为一些任务不是有一个进程实现,而是通过多个进程共同实现,所以这些进程之间按照一定的规则,互相协调合作共同实现某一任务而进行同步。

互斥:因为在多个程序并发执行时,由于共享系统资源,对于临界资源,多个进程只能进行互斥的访问,即每次只能允许一个进程访问,所以这些进程对于该资源的访问就形成了相互制约的关系。

# 二、 同步互斥方法说明

# 2.1 互斥锁同步互斥方法说明

当多个线程对公共资源进行访问和写入时,加上互斥锁以保证数据不会被多个线程操作时而混乱。下面以简单读,写操作来说明。一个线程从共享的缓冲区中读取操作,另一个线程向缓冲区中写数据,保证不会对共享缓冲区同时读取和写入,对共享缓冲区的访问加上互斥锁实现。通过线程函数 pthread_create () 在主进程中创建两个线程,一个进行读取,另一个进行写入,如果缓冲区为空,那么就向缓冲区中写入数据,并将 buffer_has_sem 设置为 1,让读线程可以读取数据。读线程则先判断缓冲区是否为空,不为空为输出数据。

# 2.2 条件变量同步互斥方法说明

条件变量是对互斥锁的补充,它的作用是用于多线程之间关于共享数据状态变化的通信,它允许线程阻塞并等待另一个线程发送的信号。当收到信号时,阻塞的线程就被唤醒并试图锁定与之相关的互斥锁。以哲学家就餐例子说明。哲学家拿到左右的两只筷子就可以开始吃饭,吃完饭放下筷子思考,利用互斥锁和条件变量进行同步互斥。因为对筷子数量的访问属于临界资源访问,所以对 i 号哲学家拿筷子进行上锁,拿完后便开锁。当 i 号哲学家拿不到左右两只筷子时,便通过条件变量开始等待,其他哲学吃完放下筷子后可以发送信号给该哲学家,当他再次发现筷子可用时便开始吃饭。以通信的方式来实现两者的同步互斥。

# 2.3 信号量同步互斥方法说明

信号量是一个用于表示资源数目的整型量 S,它仅能通过两个标准的原子操作 wait (S) 和 signal (S) 来访问,它是具有一个等待队列的计数器,属于临界资源的一种,要获取信号量资源,则对信号量进行 - 1 操作,要释放信号量资源,则对信号量资源进行 + 1 操作。以读者写者问题进行说明。读和写操作不能同时进行。允许多个读者同时读取数据,通过设置 rw_mutex 信号量来实现读写进程的互斥访问,read_cound 来统计读者的数量,mutex 用于对 read_cound 同步互斥的访问。一旦一个读者获得了读锁,其他的读者也可以获取这个读锁。但是,想要获取写锁的线程,就必须等到所有的读者都结束。

# 2.4 屏障同步互斥方法说明

屏障是用户协调多个线程并行工作的同步机制。当线程到达屏障点时,它不能继续,直到所有其他的线程也已经到这一点。当最后的线程到达屏障点时,所有线程被释放,并且能够恢复并发执行。使用 pthread_barrier_init () 函数对屏障进行初始化,使用 count 参数指定,在允许所有线程继续运行之前,必须到达屏障的线程数目。使用 pthread_barrier_wait () 函数来表明,线程已工作完成,准备等所有其他线程赶上来。以选手评分例子说明。在主线程中创建 3 个子线程,一个求最高分,另一个求最低分,最后一个线程去掉最高分和最低分后求其平均值,该线程在求最后平均分时必须要等到另外两个子线程求出最高分和最低分。求出的最后得分在主线程中输出。

# 三、 同步互斥方法实现

# 3.1 互斥锁同步互斥方法实现

# 3.1.1 互斥锁同步互斥示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>

#define FALSE 0
#define TRUE 1
char buffer[256];//定义共享缓冲区
int buffer_has_item=0;//判断缓冲区是否有数据
int retflag=FALSE;
pthread_mutex_t mutex;//定义互斥锁

void readfun()
{
while(1)
{
if(retflag)
return;
pthread_mutex_lock(&mutex);//对读操作进行上锁
if(buffer_has_item==1)
{
printf("reader:%s\n",buffer);
buffer_has_item=0;
}
pthread_mutex_unlock(&mutex);//对读操作进行开锁
}
}

void writefun()
{
int i=0;
while(1)
{
if(i==5)//写入5个数据就退出
{
retflag=TRUE;
return;
}
pthread_mutex_lock(&mutex);//对写入操作进行上锁
if(buffer_has_item==0)
{
printf("writer:%c\n",i+’a’);
sprintf(buffer,"%c",i+’a’);
buffer_has_item=1;
}
pthread_mutex_unlock(&mutex);//对写入操作进行开锁
}
}

int main()
{
//定义两个线程ID
pthread_t reader;
pthread_t writer;
pthread_mutex_init(&mutex,NULL);//初始化互斥锁
//创建两个子线程
pthread_create(&reader,NULL,(void*)&readfun,NULL);
pthread_create(&writer,NULL,(void*)&writefun,NULL);
//等待两个子线程结束
pthread_join(reader,NULL);
pthread_join(writer,NULL);
return 0;
}
# 编译后运行:

# 运行结果:

# 3.1.2 互斥锁同步互斥关键代码说明

(1)函数说明:

​ pthread_mutex_inti (&mutex,NULL); // 初始化互斥锁

​ pthread_mutex_lock (&mutex); // 上锁

​ pthread_mutex_unlock (&mutex); // 开锁

​ pthread_join (reader,NULL); // 等待写入线程结束

​ pthread_join (writer,NULL);// 等待读取线程结束

(2)临界区资源 buffer_has_item

​ if(buffer_has_item==1){

​ printf(“read:%s\n”,buffer);

​ buffer_has_item=0;

​ }

​ if(buffer_has_item==0){

​ printf(“write:%c\n”,i+’a’);

​ sprintf(buffer,"%c",i+’a’);

​ buffer_has_item=1;

​ }

# 3.2 互斥锁同步互斥方法实现

# 3.2.1 条件变量同步互斥示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#include<stdlib.h>

#define N 6
int philosopher_num=0;//定义想吃的哲学家的数量
int chopstick[N]={1,1,1,1,1,1};//初始化筷子数量

pthread_mutex_t mutex;//定义对拿筷子的互斥访问
pthread_mutex_t num;//定义对philosopher_num的互斥访问
pthread_cond_t cond_Var;//定义条件变量

void pick_forks(int i)
{
pthread_mutex_lock(&num);//对临界资源上锁
philosopher_num++;
printf("%d号哲学家想吃饭,共有%d个哲学家想吃饭\n",i,philosopher_num);
pthread_mutex_unlock(&num);
}

void return_forks(int i)
{
pthread_mutex_lock(&num);//对临界资源上锁
philosopher_num--;
pthread_mutex_unlock(&num);
printf("%d号哲学家已吃完开始思考\n",i);
}

void* philosopher(void* arg)
{
int i=*(int*)arg;//哲学家编号
pick_forks(i);
pthread_mutex_lock(&mutex);
while(chopstick[i]==0||chopstick[(i+1)%N]==0)
pthread_cond_wait(&cond_Var,&mutex);//判断i号哲学家左右两边是否有筷子,如果没有就等待
chopstick[i]--;
chopstick[(i+1)%N]--;
printf("%d eating\n",i);//哲学家拿到筷子便开始吃饭
pthread_mutex_unlock(&mutex);
pthread_mutex_lock(&mutex);
chopstick[i]++;
chopstick[(i+1)%N]++;
return_forks(i);//哲学家吃完饭放下筷子开始思考
pthread_cond_signal(&cond_Var);//i号哲学家吃完,告诉其他哲学家筷子可用
pthread_mutex_unlock(&mutex);
pthread_exit(0);
}

int main(int argc,char* argv[])
{
int i,arr[N]={0,1,2,3,4,5};//定义哲学家编号
pthread_mutex_init(&mutex,NULL);
pthread_mutex_init(&num,NULL);
pthread_cond_init(&cond_Var,NULL);
pthread_t phil[N];
for(i=0;i<N;i++)
{
pthread_create(phil+i,NULL,(void*)&philosopher,&arr[i]);//创建六个哲学家线程
}
for(i=0;i<N;i++)//等待子线程结束
{
pthread_join(phil[i],NULL);
}
return 0;
}
# 编译并执行:

# 执行结果:

# 3.2.2 条件变量同步互斥关键代码说明

(1)想吃饭的哲学家数量为临界资源,对其进行上锁

​ pthread_mutex_lock (&num);// 对临界资源上锁
​ philosopher_num–;
​ pthread_mutex_unlock(&num);

(2)哲学家进行吃饭时,通过 pthread_mutex_lock (&mutex) 对哲学家访问筷子临界资源进行上锁。当拿不到左右筷子时进行等待。while 循环是为了被唤醒后继续进行条件判断。当条件满足时 wait () 将线程阻塞后会释放互斥锁 lock, 以便其他进程能访问共享变量。别唤醒后进行上锁,以免其他线程进入。

​ pthread_mutex_lock(&mutex);

​ while(chopstick[i]==0||chopstick[(i+1)%N]==0)

pthread_cond_wait(&cond_Var,&mutex);

chopstick[i]–;

chopstick[(i+1)%N]–;

printf ("% d eating\n",i);// 哲学家拿到筷子便开始吃饭

pthread_mutex_unlock(&mutex);

(3)哲学家吃完饭,放下筷子然后通过 pthread_cond_signal (&cond_Var) 发送信号告诉其他哲学家他左右两边筷子可用,让等待的线程可以继续运行

pthread_mutex_lock(&mutex);

chopstick[i]++;

chopstick[(i+1)%N]++;

return_forks (i);// 哲学家吃完饭放下筷子开始思考

pthread_cond_signal (&cond_Var);//i 号哲学家吃完告诉其他哲学家筷子可用

pthread_mutex_unlock(&mutex);

# 3.3 信号量同步互斥方法实现

# 3.3.1 信号量同步互斥示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#include<semaphore.h>

sem_t rw_mutex;//用于对读写进程互斥的访问
sem_t mutex;//用于对read_count变量同步互斥的访问
int read_count=0;

void* reading(void* arg)
{
sem_wait(&mutex);//对临界资源read_count进行上锁
read_count++;
if (read_count==1)//第一个读者拿到锁后,其他读者可以直接进入
sem_wait(&rw_mutex);
sem_post(&mutex);
printf("%d号读者正在读取数据!\n",*(int*)arg);
sem_wait(&mutex);
read_count--;
if(read_count==0)//最后一个读者释放锁
sem_post(&rw_mutex);
sem_post(&mutex);
pthread_exit(0);
}

void* writing(void* arg)
{
while(1)
{
sem_wait(&rw_mutex);//争夺互斥量,与读进程进行互斥的访问
printf("正在写入数据!\n");
sem_post(&rw_mutex);
sleep(2);
}
}

int main()
{
int i,reader_id[5]={1,2,3,4,5};//定义读者编号
sem_init(&rw_mutex,0,1);
sem_init(&mutex,0,1);
pthread_t reader[5],writer;
//创建写者线程
pthread_create(&writer,NULL,(void*)&writing,NULL);
//创建5个读者线程
for(i=0;i<5;i++)
{
pthread_create(reader+i,NULL,(void*)&reading,&reader_id[i]);
sleep(1);
}
//等待读者线程结束,写线程中有while循环,不用等待它结束
for(i=0;i<5;i++)
{
pthread_join(reader[i],NULL);
}
sem_destroy(&rw_mutex);
sem_destroy(&mutex);
return 0;
}
# 编译后执行:

# 运行结果:

# 3.3.2 信号量同步互斥关键代码说明

(1)对读者线程,首先对 read_count 临界资源上锁,并将读者数量加 1,如果是第一个读者拿到锁,那么其他读者不再需要与写者进行争夺锁便可以读取数据。

当剩最后一个读者时,读完数据后将读 / 写进程锁释放。(int) arg 为线程 (读者) 编号。

sem_wait(&mutex);

read_count++;

if (read_count==1)

sem_wait(&rw_mutex);

sem_post(&mutex);

printf ("% d 号读者正在读取数据!\n",(int)arg);

sem_wait(&mutex);

read_count–;

if(read_count==0)

sem_post(&rw_mutex);

sem_post(&mutex);

(2)写者进程通过 while 循环一直进行写操作,争躲到读 / 写进程锁就可以进行数据写入

​ while(1)

​ {

​ sem_wait(&rw_mutex);

​ printf (“正在写入数据!\n”);

​ sem_post(&rw_mutex);

​ }

(3)sem_post () 函数(函数原型 int sem_wait (sem_t * sem);)
作用是给信号量的值加上一个 “1”。 当有线程阻塞在这个信号量上时,调用这个函数会使其中一个线程不在阻塞,选择机制是有线程的调度策略决定的。

​ sem_wait () 函数(函数原型 int sem_wait (sem_t * sem);)
它的作用是从信号量的值减去一个 “1”,但它永远会先等待该信号量为一个非零值才开始做减法。

# 3.4 屏障同步互斥方法实现

# 3.4.1 屏障同步互斥示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>

#define N 7
pthread_barrier_t barrier;//定义屏障
double ave;//平均分
int min,max;//最低分和最高分

//计算去掉最低分和最高分之后的平均分
void* average(int *arg)
{
int i,sum=0;
for(i=0;i<N;i++)
{
sum+=arg[i];
}
/*等待两个子线程找出最低分和最高分后,然后再来求平均分*/
pthread_barrier_wait(&barrier);//到达屏障点,停止等待
/*等所有线程到达屏障点后,继续运行求出平均值在主线程中输出*/
ave=(sum-min-max)/(N-2.0);
pthread_exit(0);
}
//找到最低分
void* mininum(int *arg)
{
int i,j;
min=arg[0];
for(i=0;i<N;i++)
{
if(arg[i]<min)
{
min=arg[i];
}
}
printf("The mininum value is %d\n",min);
pthread_barrier_wait(&barrier);//到达屏障点,停止等待
pthread_exit(0);
}
//找到最高分
void* maxinum(int *arg)
{
int i,j;
max=arg[0];
for(i=0;i<N;i++)
{
if(arg[i]>max)
{
max=arg[i];
}
}
printf("The maxinum value is %d\n",max);
pthread_barrier_wait(&barrier);//到达屏障点,停止等待
pthread_exit(0);
}

int main(int argc,char* argv[])
{
if(argc!=8)
{
printf("请输入%d个数值\n",N);
return 0;
}
int i,array[N];
for(i=0;i<N;i++)//将命令行输入的参数存入数组中
{
array[i]=atoi(argv[i+1]);
}
pthread_t tid1,tid2,tid3;
/*初始化屏障,设置需到达屏障的数目为3(3个子线程)*/
pthread_barrier_init(&barrier,NULL,4);
pthread_create(&tid1,NULL,(void*)&average,array);
pthread_create(&tid2,NULL,(void*)&maxinum,array);
pthread_create(&tid3,NULL,(void*)&mininum,array);
pthread_join(tid1,NULL);//等待tid1线程求出平均值,以免主线程先结束
printf("The final score is %.1f\n",ave);
pthread_barrier_destroy(&barrier);//释放屏障资源
return 0;
}
# 编译代码:

# 运行结果:

# 3.4.2 屏障同步互斥关键代码说明

(1)对于求平均分线程,要等待两个子线程找出最低分和最高分后,才能求最后的得分。

通过设置屏障,等待其他线程到达该点后,继续并发执行求出最后的平均分 ave。最后在主线程中输出平均分。

1
2
3
4
5
6
7
8
9
10
for(i=0;i<N;i++)
{

sum+=arg[i];

}

pthread_barrier_wait(&barrier);//到达屏障点,停止等待

ave=(sum-min-max)/(N-2.0);

(2) 对于另外两个子线程,当求出最大值和最小值后,表明自己的工作已经完成。并输出最大值和最小值。

​ printf(“The maxinum value is %d\n”,max);

​ pthread_barrier_wait(&barrier);

​ printf(“The mininum value is %d\n”,min);

​ pthread_barrier_wait(&barrier);