我们通过下例程序,引出线程同步的概念:
该代码功能为两个线程不断向STDOUT打印hello world,子线程是小写的,主线程是大写的
#include<stdio.h>
#include<string.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>
void *tfn(void *arg)
{
srand(time(NULL));
while(1)
{
printf("hello ");
sleep(rand()%3);//模拟长时间操作共享内存,导致cpu易主,产生与时间有关的错误
printf("world\n");
sleep(rand()%3);
}
return NULL;
}
int main(void)
{
//父线程和子线程的共享资源为STDOUT
pthread_t tid;
srand(time(NULL));
pthread_create(&tid,NULL,tfn,NULL);
while(1)
{
printf("HELLO ");
sleep(rand()%3);
printf("WORLD\n");
sleep(rand()%3);
}
return 0;
}
运行结果:
可以看到打印出来是混乱的,这是因为子线程可能打印了一半后,时间片轮完阻塞等待cpu,然后主控线程得到了cpu的使用权,接着往屏幕打印,所以看起来是混乱的,这里就涉及到线程同步问题了。
类似于上述例子,当多个线程来访问同一共享资源时,非常容易出现线程安全问题(比如多个线程都在操作同一数据导致数据不一致),所以我们用同步机制来解决这些问题。
**同步机制就是:通过一些机制实现对共享资源的有序访问。即多个线程按一种规则来对共享资源顺序访问(在对共享资源操作时,必须保证没有其他线程操作,若在访问共享数据时有其他线程正在对其操作,则需要等待其操作完成再访问)。**这里可以看这篇博客点击此处,里面讲的很清楚。
我们通过互斥锁来解决上述代码的问题:
互斥锁的数据类型为pthread_mutex_t
,是一个结构体,我们可以把它当作值只有0和
1的数来简化看待,为1时状态为加锁状态,0为不加锁状态。
我们对互斥锁的操作都需要调用函数来进行,有以下函数:
// 初始化互斥锁
int pthread_mutex_init (pthread_mutex_t * restrict mutex , \
const pthread_mutexattr_t * restrict attr ) ;
//销毁锁
int pthread_mutex_destroy (pthread_mutex_t * mutex ) ;
// 加锁
int pthread_mutex_lock (pthread_mutex_t *mutex) ;
// 解锁
int pthread_mutex_unlock (pthread_mutex_t *mutex) ;
// 尝试加锁
int pthread_mutex_trylock (pthread_mutex_t *mutex) ;
我们把对共享资源操作的代码称之为临界区,一般再进入临界区之前会加锁,离开临界区后会解锁。若加锁的时候这把锁已经被别的线程占有(即已经有线程加锁了),则该线程会阻塞等待,当加锁的线程操作完成解锁后,会唤醒阻塞在锁上的进程,这样就实现了线程间的同步。但是需要注意的是,所有的锁机制都是一把建议锁,也就是操作系统给你提供了锁机制,但并不强制要求。比如说一个线程已经加锁了,这个时候另一个线程没有加锁就对共享资源访问也是会成功的,操作系统并不会因为你加了锁就会限制其他线程访问共享资源。
使用方法:在全局建一把锁,在主控线程中对互斥锁初始化,在所有临界区前后加锁,解锁。
下面来对开头的代码引入互斥锁解决bug,来说明互斥锁的应用:
1 #include<stdio.h>
2 #include<string.h>
3 #include<pthread.h>
4 #include<stdlib.h>
5 #include<unistd.h>
6
7 void *tfn(void *arg)
8 {
9 srand(time(NULL));
10
11 while(1)
12 {
13 pthread_mutex_lock((pthread_mutex_t *)arg);//加锁
14
15 printf("hello ");
16 sleep(rand()%3);//模拟长时间操作共享内存,导致cpu易主,产生与时间有关的错误
17 printf("world\n");
18
19 pthread_mutex_unlock((pthread_mutex_t *)arg);//解锁
20 sleep(rand()%3);
21 }
22 return NULL;
23 }
24 int main(void)
25 {
26 //父线程和子线程的共享资源为STDOUT
27
28 pthread_t tid;
29 srand(time(NULL));
30
31 pthread_mutex_t mutex;//建锁
32 pthread_mutex_init(&mutex,NULL);//初始化锁
33 pthread_create(&tid,NULL,tfn,(void*)&mutex);//一般做法是把mutex设为全局变量,这里设成局部变量通过传参也是可以的
34 while(1)
35 {
36 pthread_mutex_lock(&mutex);//加锁
37
38 printf("HELLO ");
39 sleep(rand()%3);
40 printf("WORLD\n");
41
42 pthread_mutex_unlock(&mutex);//解锁
43
44 sleep(rand()%3);
45 }
46
47 pthread_mutex_destroy(&mutex);//释放锁
48 return 0;
49 }