一、 线程同步的问题
1、全局变量实现同步
由于同一进程中的线程之间相互共享了全局变量,而且线程之间的运行是没有顺序的,因此对于某些共享的全局变量的修改可能引起其他线程变得不可以预测,因此在多线程的编程当中一定要考虑线程之间的同步问题。
下面先介绍一个简单的实现进程同步的方法,在这里通过一个全局变量控制各个线程之间的运行顺序。这个程序主要实现了首先是主线程输出1,然后子线程输出2这样交替的输出。
程序源码如下:
#include
#include
#include
#include
void *thread_function(void *arg);
char message[] = "Hello world";
int run_now = 1;
int main(int argc, char *argv[])
{
int res, i;
pthread_t p_thread;
void *thread_result;
res = pthread_create(&p_thread, NULL, thread_function, (void *)message);
if(res != 0)
{
perror("Thread creation failed");
exit(EXIT_FAILURE);
}
printf("Waiting for thread to finish.....\n");
for(i=0; i< 10; i++)
{
if(run_now == 1)
{
printf("1");
run_now = 2;
}
else
{
sleep(1);
}
}
res = pthread_join(p_thread, &thread_result);
if(res != 0)
{
perror("Thread join failed");
exit(EXIT_FAILURE);
}
printf("Thread join, it returned %s\n", (char *)thread_result);
printf("Message is now %s\n", message);
exit(EXIT_SUCCESS);
}
void *thread_function(void *arg)
{
int i;
printf("thread_function is runing. Argument was %s\n", (char *)arg);
for(i=0; i<10; i++)
{
if(run_now == 2)
{
printf("2");
run_now = 1;
}
else
{
sleep(1);
}
}
strcpy(message, "bye!");
pthread_exit("Thank you for the cpu time");
}
编译的方法如上,运行结果是不确定的,但是在结果中输出的应该是1212这种交替序列,这是因为在程序中的run_now全局变量起的作用,这就实现了一个简单的同步。但是通过这种方法实现同步控制是有问题的,因为每个线程输出和修改run_now并不是一个原子操作,这就是这种操作的bug所在,假设还有一个线程,当主线程将run_now修改成2以后,第三个子线程得到运行它此时将run_now修改成1,那么当主线程在次获得运行权的时候还会打印1的这就是使用全局变量控制同步的最大问题,也是多线程运行所需要解决的问题就是原子操作。
2、 信号量(Semaphore)实现线程同步
1965年,Dijkstra提出了信号量的概念,之后信号量成为操作系统实现互斥与同步的一种普遍机制。信号量包含一个非负整形变量,并且带有两个原子操作post和wait。信号量的工作原理是:如果信号量的非负整形变量S大于0,wait就将其减1,如果S等于0,这wait就将调用的线程挂起,并加入到挂起的队列中。而post操作,如果有线程在信号量上阻塞,则就会唤醒挂起队列中的第一个线程;如果没有在信号量上阻塞的线程,那么就将S的值加1。
信号量的使用需要添加semaphore.h头文件,主要用到的函数有:
int sem_init(sem_t *sem, int pshared, unsigned int value); 初始化一个无名信号量函数
参数一是信号量变量;
参数二指示了这个信号量的作用范围,如果pthread=0则这个信号量的作用范围就是在这个进程当中即这个进程中的各个线程使用,如果pthread是非零值则这个信号量可以被其他能访问到它的进程使用;
参数三说明这个信号量的初始值,在这里这个value是多少,就说明可以有多少个线程同时访问这个信号量作用的临界区。
int sem_post(sem_t *sem);实现post操作
int sem_wait(sem_t *sem);实现wait操作,如果S等于0,这调用线程将被挂起,直到有线程执行post操作唤醒它。
int sem_trywait(sem_t *sem);这个函数与wait函数的不同之处在于,S等于0时,它不会被挂起而是继续执行下面的操作,也就是非阻塞函数。
int sem_destroy(sem_t *sem); 销毁一个无名信号量。
示例程序如下:
#include
#include
#include
#include
#include
#define SIZE 1024
void *thread_function(void *arg);
sem_t bin_sem;
char message[SIZE];
int main(int argc, char *argv[])
{
int res;
pthread_t p_thread;
void *thread_result;
sem_init(bin_sem, 0, 0);
memset(message, 0, SIZE);
res = pthread_create(&p_thread, NULL, thread_function, (void *)message);
if(res != 0)
{
perror("Thread creation failed");
exit(EXIT_FAILURE);
}
printf("please input some text\n");
while(strncmp("end", message, 3) != 0)
{
fgets(message, SIZE, stdin);
sem_post(&bin_sem);
}
printf("Waiting for thread to finish.....\n");
res = pthread_join(p_thread, &thread_result);
if(res != 0)
{
perror("Thread join failed");
exit(EXIT_FAILURE);
}
printf("Message is now %s\n", message);
sem_destroy(&bin_sem);
exit(EXIT_SUCCESS);
}
void *thread_function(void *arg)
{
while(strncmp("end", message, 3) != 0)
{
printf("you input %s \n", message);
sem_wait(&bin_sem);
}
pthread_exit("Thank you for the cpu time");
}
3、 互斥锁实现同步
互斥锁其实就是在访问临界区之前先要做开锁的动作,即去获得访问资源的权限,通过互斥锁实现的同步的临界区是一次只能被一个线程访问到的。在使用互斥锁的时候一定要当心程序出现死锁的情况。
使用互斥锁需要添加pthread.h头文件,使用到的函数如下:
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
具体函数定义就不详细介绍了。
示例程序如下:
#include
#include
#include
#include
void *thread_function(void *arg);
pthread_mutex_t work_mutex; /* protects both work_area and time_to_exit */
#define WORK_SIZE 1024
char work_area[WORK_SIZE];
int time_to_exit = 0;
int main() {
int res;
pthread_t a_thread;
void *thread_result;
res = pthread_mutex_init(&work_mutex, NULL);
if (res != 0) {
perror("Mutex initialization failed");
exit(EXIT_FAILURE);
}
res = pthread_create(&a_thread, NULL, thread_function, NULL);
if (res != 0) {
perror("Thread creation failed");
exit(EXIT_FAILURE);
}
pthread_mutex_lock(&work_mutex);
printf("Input some text. Enter 'end' to finish\n");
while(!time_to_exit) {
fgets(work_area, WORK_SIZE, stdin);
pthread_mutex_unlock(&work_mutex);
while(1) {
pthread_mutex_lock(&work_mutex);
if (work_area[0] != '\0') {
pthread_mutex_unlock(&work_mutex);
sleep(1);
}
else {
break;
}
}
}
pthread_mutex_unlock(&work_mutex);
printf("\nWaiting for thread to finish...\n");
res = pthread_join(a_thread, &thread_result);
if (res != 0) {
perror("Thread join failed");
exit(EXIT_FAILURE);
}
printf("Thread joined\n");
pthread_mutex_destroy(&work_mutex);
exit(EXIT_SUCCESS);
}
void *thread_function(void *arg) {
sleep(1);
pthread_mutex_lock(&work_mutex);
while(strncmp("end", work_area, 3) != 0) {
printf("You input %d characters\n", strlen(work_area) -1);
work_area[0] = '\0';
pthread_mutex_unlock(&work_mutex);
sleep(1);
pthread_mutex_lock(&work_mutex);
while (work_area[0] == '\0' ) {
pthread_mutex_unlock(&work_mutex);
sleep(1);
pthread_mutex_lock(&work_mutex);
}
}
time_to_exit = 1;
work_area[0] = '\0';
pthread_mutex_unlock(&work_mutex);
pthread_exit(0);
}
同步问题最重要的就是要防止程序出现死锁的情况。。。。