1. 并行与并发
- 并发是指一个处理器处理多个任务,但是在同一时刻只有一条指令执行,多个进程指令被快速的轮换执行。
- 并发是指多个处理器或者是多核处理器同时处理多个不同的任务,指在同一时刻,有多条指令在多个处理器上同时执行。
2. 线程的特性
- 同一程序中的所有所有线程均会独立执行相同程序,切共享同一份全局内存区域,其中包括初始化数据段(initialized data)
、未初始化数据段(uninitialized data),以及堆内存段(heap segment)。 - 同一程序中的多个线程可以并发执行。在多处理器环境下,多个线程可以同时并行;如果一线程因等待I/O操作而阻塞,那么其他线程可以继续运行。
- 可以提升用户的体验;如果没有多线程而是按顺序执行程序,有些用户的请求得到了及时的响应,而有些用户的请求却迟迟得不到服务器的响应。
- 多线程引发的问题:
-
多个线程共用一个变量,引起结果不唯一的情况,如下图代码示例:
#include<stdio.h> #include<pthread.h> //用来多线程执行的函数 void* handFun(void *arg) { int i = *(int *)arg; printf("i=%d\n",i); fflush(stdout); } int main(int argc, char *agrv[]) { int i = 0;//该变量使所有线程共享 int error; pthread_t tid[4]; for(i = 0; i < 4; i++) //创建4个线程,并共用i { //原本的意图是打印0-3 error = pthread_create(&tid[i],NULL,handFun,(void *)&i); if(error) { printf("create thread fail"); return -1; } } while(1); return 0; }
-
局部变量内存已回收,导致子线程访问出现段错误或成为野指针,如下代码示例:
#include<stdio.h> #include<pthread.h> #include<string.h> typedef struct { char name[256]; int age; }T_Person; //用来多线程执行的函数 void* handFun(void *arg) { T_Person *person = (T_Person *)arg; //到该语句person所占的内存没有释放,正常打印。 printf("name=%s\n",person->name); fflush(stdout); sleep(2); //到该语句person所占的内存释放,出现段错误或输出不正确的值。 printf("age=%d\n",person->age); fflush(stdout); } void testFun() { int error; pthread_t tid; T_Person person; memset(&person,0,sizeof(T_Person)); sprintf(person.name,"%s","jackoneone"); person.age = 34; error = pthread_create(&tid,NULL,handFun,(void *)&person); if(error) { printf("create thread fail"); } sleep(1); } int main(int argc, char *agrv[]) { pthread_t tid; int error; error = pthread_create(&tid,NULL,testFun,NULL); if(error) { printf("main:create thread fail"); } while(1); return 0; }
3. 线程同步
1. 互斥量
线程的的主要优势在于,能够通过访问全局变量共享信息。不过,这种便捷的共享是有代价的;必须确保多个线程不会同时修改同一个变量,或者某一线程不会读取正由其他线程修改的变量。
互斥变量有两种状态,已锁定和未锁定。任何时候,至多只有一个线程可以锁定该互斥量。试图对已经锁定的某一互斥量再次加锁,将可能阻塞线程或者报错失败,具体取决于加锁时使用的方法。
一旦线程锁定互斥量,随即成为该互斥量的所有者。只有所有者才能给互斥量解锁。一般情况下,对每一共享资源(可能由多个相关变量组成)会使用不同的互斥量。
2. 静态分配的互斥量
-
互斥量既可以像静态变量那样分配,也可以运行时动态创建。互斥量是属于
pthread_mutex_t
类型的变量。在使用之前必须对其进行初始化。对于静态分配的互斥量而言,可以使用下例所示:
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER
-
代码示例:
#include<stdio.h>
#include<pthread.h>
static int glob = 0; //该变量用来多线程共享
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; //静态分配mutex
static void* testFunc(void *arg)
{
int loops = *(int *)arg;
int local ;
int i = 0;
int s;
//锁加在这里也可以,只是增加了粒度,不利于并发
//s = pthread_mutex_lock(&mtx);//获取并锁住互斥量
for(; i < loops; i++)
{
s = pthread_mutex_lock(&mtx);//获取并锁住互斥量
if(0 != s)
{
printf("get mutex fail,exit!");
return NULL;
}
local = glob;
local++;
glob = local;
s = pthread_mutex_unlock(&mtx);//解锁互斥量
if(0 != s)
{
printf("unlock mutex fail,exit!");
return NULL;
}
}
//s = pthread_mutex_unlock(&mtx);//解锁互斥量
return NULL;
}
int main()
{
pthread_t t1,t2;
int s;
int loop = 100;
s = pthread_create(&t1,NULL,testFunc,(void *)&loop);//创建线程1
if(s != 0)
{
printf("create pthread1 fail!");
return -1;
}
s = pthread_create(&t2,NULL,testFunc,(void *)&loop);//创建线程2
if(s != 0)
{
printf("create pthread2 fail!");
return -1;
}
s = pthread_join(t1,NULL); //让主线程等待线程1
if(s != 0)
{
printf("pthread_join1 fail");
return -1;
}
s = pthread_join(t2,NULL);//让主线程等待线程2
if(s != 0)
{
printf("pthread_join2 fail");
return -1;
}
printf("glob=%d \n",glob);
return 0;
}
- 执行流程:
3. 动态分配的互斥量
- 在以下情况必须使用函数
pthread_mutex_init()
,而非静态初始化互斥量:
- 动态分配于堆中的互斥量。
- 互斥量是在栈中分配的自动变量。
- 初始化经由静态分配,且不使用默认属性的互斥量。
-
动态分配的互斥量使用示例:
#include<stdio.h> #include<pthread.h> #include<string.h> typedef struct { int count ; pthread_mutex_t mtx; }T_ClickInfo; static void* testFunc(void *arg) { int i = 0; int s; T_ClickInfo *clickInfo = (T_ClickInfo *)arg; for(i = 0; i< 50;i++) { s = pthread_mutex_lock(&clickInfo->mtx);//获取并锁住互斥量 if(0 != s) { printf("get mutex fail,exit!"); return NULL; } clickInfo->count++; s = pthread_mutex_unlock(&clickInfo->mtx);//解锁互斥量 } } int main() { pthread_t t1,t2; int s; T_ClickInfo *clickInfo; clickInfo = (T_ClickInfo *)malloc(sizeof(T_ClickInfo)); memset(clickInfo,0,sizeof(T_ClickInfo)); clickInfo->count = 0; s = pthread_mutex_init(&clickInfo->mtx,NULL); //初始化锁 if(s != 0) { printf("init mutex"); return -1; } s = pthread_create(&t1,NULL,testFunc,(void *)clickInfo);//创建线程1 if(s != 0) { printf("create pthread1 fail!"); return -1; } s = pthread_create(&t2,NULL,testFunc,(void *)clickInfo);//创建线程2 if(s != 0) { printf("create pthread2 fail!"); return -1; } s = pthread_join(t1,NULL); //让主线程等待线程1 if(s != 0) { printf("pthread_join1 fail"); return -1; } s = pthread_join(t2,NULL);//让主线程等待线程2 if(s != 0) { printf("pthread_join2 fail"); return -1; } printf("count= %d\n",clickInfo->count); s = pthread_mutex_destroy(&clickInfo->mtx); //销毁锁 if(s != 0) { printf("destroy mutex fail"); return -1; } free(clickInfo); return 0; }
4. 条件变量
- 互斥量防止多个进程同时访问同一共享内存变量。条件变量允许一个线程就某个共享变量(或其他共享资源)的状态变化通知其他线程,并让其他线程等待(堵塞于)这一通知。
- 条件变量的数据类型是
pthread_count_t
。类似于互斥量,使用条件变量前必须对其进行初始化。 - 条件变量并不保存状态信息,只是传递应用程序状态信息的一种通讯机制。发送信号时若无任何线程在等待该条件变量,这个信号也就不了了之。线程如在此后等待该条件变量,只有当再次受到此变量的下一信号时,方可解除阻塞状态。
- 代码示例
#include<stdio.h>
#include<pthread.h>
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; //静态分配mutex
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;//静态分配condition
static int appleCount = 0;
static void* producerFun()
{
int s;
while(1) //不停生产苹果
{
s = pthread_mutex_lock(&mtx);//获取并锁住互斥量
if(0 != s)
{
printf("producer get mutex fail,exit!");
return NULL;
}
appleCount++;
printf("producer:appCount=%d\n",appleCount);
fflush(stdout);
sleep(1);
s = pthread_mutex_unlock(&mtx);//解锁互斥量
if(0 != s)
{
printf("producer unlock mutex fail,exit!");
return NULL;
}
//pthread_cond_signal()与pthread_cond_broadcast()的区别:
//1.pthread_cond_signal()函数只保证唤醒至少一条遭到阻塞的线程,
//而pthread_cond_broadcast()则会唤醒所有遭阻塞的线程。
//2.pthread_cond_signal()更高效。
//3.pthread_cond_broadcast()总能产生真确的结果。
s = pthread_cond_signal(&cond); //唤醒消费者
//s = pthread_cond_broadcast();
if(0 != s)
{
printf("producer signal fail,exit!");
return NULL;
}
}
return NULL;
}
static void* consumerFun()
{
int s;
while(1) //不断的吃苹果
{
s = pthread_mutex_lock(&mtx);//获取并锁住互斥量
if(0 != s)
{
printf("consumer get mutex fail,exit!");
return NULL;
}
while(0 == appleCount) //苹果没有了
{
//pthread_cond_wait执行的动作:
//1.解锁互斥量mutex
//2.堵塞调用线程,直到另一线程就cond发出信号
//3.重新锁定mutex
s = pthread_cond_wait(&cond,&mtx);
//printf("consumer:I block!");
//fflush(stdout);
if(0 != s)
{
printf("consumer wait fail,exit!");
return NULL;
}
}
while(appleCount > 0)
{
printf("consumer:appCount=%d\n",appleCount);
fflush(stdout);
appleCount--;
sleep(1);
}
s = pthread_mutex_unlock(&mtx);//解锁互斥量
if(0 != s)
{
printf("consumer unlock mutex fail,exit!");
return NULL;
}
}
}
int main()
{
pthread_t t1,t2;
int s;
s = pthread_create(&t1,NULL,producerFun,NULL);//创建线程1
if(s != 0)
{
printf("create pthread1 fail!");
return -1;
}
s = pthread_create(&t2,NULL,consumerFun,NULL);//创建线程2
if(s != 0)
{
printf("create pthread2 fail!");
return -1;
}
s = pthread_join(t1,NULL); //让主线程等待线程1
if(s != 0)
{
printf("pthread_join1 fail");
return -1;
}
s = pthread_join(t2,NULL);//让主线程等待线程2
if(s != 0)
{
printf("pthread_join2 fail");
return -1;
}
return 0;
}
4. 线程状态转换图
5. 总结
- 线程提供的强大共享是有代价的。多线程应用程序必须使用互斥量和条件变量等同步原语来协调对共享变量的访问。互斥量提供了对共享变量的独占式访问。条件变量允许一个或多个线程等候通知:其他线程改变了共享变量的状态。
- 使用互斥量可以实现大部分的函数的线程安全,不过由于互斥量的加、解锁开销,故而也带来了性能的下降。如能避免使用全局变量或静态变量,可重入函数则无需使用互斥量即可实现线程安全。