1. 一个线程实验的问题
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <ctype.h>
#include <pthread.h>
#define MAX_THREAD 3 /* 线程的个数 */
unsigned long long main_counter, counter[MAX_THREAD];
pthread_mutex_t mutex;
void* thread_worker(void*);
int main(int argc,char* argv[])
{
int i, rtn, ch;
pthread_t pthread_id[MAX_THREAD] = {0}; /* 存放线程id*/
for (i=0; i<MAX_THREAD; i++)
{
int *t = malloc(4);// 用malloc,避免下面count[i]++的时候i的值改变(i在下面函数赋给了thread_num)
*t = i;
pthread_create(&pthread_id[i], NULL, thread_worker, (void *)t ); //新建子线程
}
do
{
/* 用户按一次回车执行下面的循环体一次。按q退出 */
unsigned long long sum = 0;
/* 求所有线程的counter的和 */
for (i=0; i<MAX_THREAD; i++)
{
/* 求所有counter的和 */
pthread_mutex_lock(&mutex);
sum += counter[i]; // ---- 2
printf("%llu ", counter[i]);
}
printf("%llu/%llu", main_counter, sum);
pthread_mutex_unlock(&mutex);
}while ((ch = getchar()) != 'q');
return 0;
}
void* thread_worker(void* p)
{
int thread_num;
thread_num = *(int *)p;
/* 无限循环 */
for(;;)
{
thread_num = *(int *)p;
if(pthread_mutex_lock(&mutex) < 0)
{
printf("lock error\n");
exit(0);
}
counter[thread_num]++; /* 本线程的counter加一 */
main_counter++; /* 主counter 加一 */ //---- 1
if(pthread_mutex_unlock(&mutex) < 0)
{
printf("unlock error\n");
exit(0);
}
}
}
现象:
若直接运行此程序,会产生多种错误(与预想的每行最后两列值相等不符,且会有大于和小于两种情况的发生)。
先说第一种小于的情况,因为自加
( 代码中1处 ) 的操作并非原子操作,需要t = i
, t += 1
, i = t
三步才可以完成自加,即可以被打断其运行,若一个线程在前两步被打断(设此时t = 100),而其他线程又对i进行自加到了i = 120
,此时第一个线程继续运行,问题出现了,现在t加1后再赋给i,此后i的自加是从101开始的,并非从预想的120开始自加,所以i最终的值会小于实际应该的值。
再说第二种大于的情况,问题在代码中2处,同样因为线程调度的原因,线程可能在sum把count[i]加和后,线程停止,其他线程运行,导致在其他线程中count[i]的值又自加了(例如代码1处),而在输出时则输出已经加和的sum,而不是数值改变后的和,所以数值看起来会变小。
解决办法:
既然我们已经找到了问题发生的原因,也就是某线程的非原子操作可能会发生中断,导致其他线程修改了本线程将要使用变量的值,那么我们就要想办法让本线程在操作完之前其他线程不会改变将要使用或已使用变量的值,所以互斥量在此时就发挥出了作用。设置互斥量后就保证了当前进程对某些变量的“绝对占有权”,避免了其他线程对其影响。
只需改1、2处代码即可:
/*修改的第一部分*/
counter[thread_num]++;
pthread_mutex_lock(&mutex); //加锁
main_counter++; //---- 1
pthread_mutex_unlock(&mutex);//解锁
/*修改的第二部分*/
do
{
unsigned long long sum = 0;
pthread_mutex_lock(&mutex); //加锁
for (i=0; i<MAX_THREAD; i++)
{
sum += counter[i]; // ---- 2
printf("%llu ", counter[i]);
}
printf("%llu/%llu", main_counter, sum);
pthread_mutex_unlock(&mutex); //解锁
}while ((ch = getchar()) != 'q');
至此,已经修改完毕。
2. 死锁问题
该问题只要注意加锁的顺序要对应即可。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <ctype.h>
#include <pthread.h>
#define LOOP_TIMES 10000
/*用宏PTHREAD_MUTEX_INITIALIZER来初始化 */
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
void* thread_worker(void*);
void critical_section(int thread_num, int i);
int main(void)
{
int rtn, i;
pthread_t pthread_id = 0; /* 存放子线程的id */
rtn = pthread_create(&pthread_id,NULL, thread_worker, NULL );
if(rtn != 0)
{
printf("pthread_create ERROR!\n");
return -1;
}
for (i=0; i<LOOP_TIMES; i++)
{
/*代码段1*/
pthread_mutex_lock(&mutex1); // 先锁1,后锁2
pthread_mutex_lock(&mutex2);
critical_section(1, i);
pthread_mutex_unlock(&mutex2);
pthread_mutex_unlock(&mutex1);
}
pthread_mutex_destroy(&mutex1);
pthread_mutex_destroy(&mutex2);
return 0;
}
void* thread_worker(void* p)
{
int i;
for (i=0; i<LOOP_TIMES; i++)
{
/*代码段2*/
pthread_mutex_lock(&mutex2); // 先锁2,后锁1
pthread_mutex_lock(&mutex1);
critical_section(2, i);
pthread_mutex_unlock(&mutex2);
pthread_mutex_unlock(&mutex1);
}
}
void critical_section(int thread_num, int i)
{
printf("Thread%d: %d\n", thread_num,i);
}
现象及原因:
该代码由于加锁顺序不同,可能一个线程在代码段1处先给mutex1
加锁,将mutex1阻塞,然后暂停运行,被另一线程抢夺了使用cpu的权限,而另一线程刚好又在代码段2处对mutex2
进行了加锁,将mutex2阻塞。此时mutex1需要调用mutex2,而mutex2需要调用mutex1,然而更加不幸的是两者都被阻塞,等待对方被释放,悲剧就此产生,两个固执的线程都在等待对方收手,而这是不可能发生的事,所以就这样僵持下去,死锁现象也就出现了。
解决方法:
很简单,将加锁顺序修改的相同即可。都先锁1,后锁2,反之亦可。