Linux系统编程——线程(2)
# Linux系统编程——线程(2)
前情提要: Linux系统编程——线程(1)
同步概念
所谓同步,即同时起步,协调一致。不同的对象,对“同步”的理解方式略有不同。如,设备同步,是指在两个设备之间规定一个共同的时间参考;数据库同步,是指让两个或多个数据库内容保持一致,或者按需要部分保持一致;文件同步,是指让两个或多个文件夹里的文件保持一致。
而编程中、通信中所说的同步与生活中大家印象中的同步概念略有差异。“同”字应是指协同、协助、互相配合。主旨在协同步调,按预定的先后次序运行。
线程同步
同步即协同步调,按预定的先后次序运行。
线程同步,指一个线程发出某一功能调用时,在没有得到结果之前,该调用不返回。同时其它线程为保证数据一致性,不能调用该功能。
举例:
#include <func.h>
//主线程与子线程各加1000万
#define N 10000000
void* threadFunc(void *p)
{
int *num=(int*)p;
int i;
for(i=0;i<N;i++)
{
*num+=1;
}
printf("I am child thread\n");
return NULL;
}
int main()
{
pthread_t pthID;//线程ID
int ret;
int num=0;
ret=pthread_create(&pthID,NULL,threadFunc,&num);
if(ret!=0)
{
printf("pthread_create:%s\n",strerror(ret));
return -1;
}
int i;
for(i=0;i<N;i++)
{
num=num+1;
}
pthread_join(pthID,NULL);
printf("I am main thread,%d\n",num);
return 0;
}
“同步”的目的,是为了避免数据混乱,解决与时间有关的错误。实际上,不仅线程间需要同步,进程间、信号间等等都需要同步机制。
因此,所有“多个控制流,共同操作一个共享资源”的情况,都需要同步。
数据混乱原因:
-
资源共享(独享资源则不会)
-
调度随机(意味着数据访问会出现竞争)
-
线程间缺乏必要的同步机制。
以上3点中,前两点不能改变,欲提高效率,传递数据,资源必须共享。只要共享资源,就一定会出现竞争。只要存在竞争关系,数据就很容易出现混乱。
所以只能从第三点着手解决。使多个线程在访问共享资源的时候,出现互斥。
互斥量mutex
Linux中提供一把互斥锁mutex(也称之为互斥量)。
每个线程在对资源操作前都尝试先加锁,成功加锁才能操作,操作结束解锁。
资源还是共享的,线程间也还是竞争的。
pthread_mutex_init函数
pthread_mutex_destroy函数
pthread_mutex_lock函数
pthread_mutex_trylock函数
pthread_mutex_unlock函数
以上5个函数的返回值都是:成功返回0, 失败返回错误号。
pthread_mutex_t 类型,其本质是一个结构体。为简化理解,应用时可忽略其实现细节,简单当成整数看待。
pthread_mutex_t mutex; 变量mutex只有两种取值1、0。
pthread_mutex_init函数
初始化一个互斥锁(互斥量) ---> 初值可看作1
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
参1:传出参数,调用时应传 &mutex
restrict关键字:只用于限制指针,告诉编译器,所有修改该指针指向内存中内容的操作,只能通过本指针完成。不能通过除本指针以外的其他变量或指针修改
参2:互斥量属性。是一个传入参数,通常传NULL,选用默认属性(线程间共享)。 参APUE.12.4同步属性
-
静态初始化:如果互斥锁 mutex 是静态分配的(定义在全局,或加了static关键字修饰),可以直接使用宏进行初始化。e.g. pthead_mutex_t muetx = PTHREAD_MUTEX_INITIALIZER;
-
动态初始化:局部变量应采用动态初始化。e.g. pthread_mutex_init(&mutex, NULL)
pthread_mutex_destroy函数
销毁一个互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
#include <func.h>
int main()
{
pthread_mutex_t mutex;
int ret;
ret=pthread_mutex_init(&mutex,NULL);
THREAD_ERROR_CHECK(ret,"pthread_mutex_init");
ret=pthread_mutex_destroy(&mutex);
THREAD_ERROR_CHECK(ret,"pthread_mutex_destroy");
return 0;
}
pthread_mutex_lock函数
加锁。可理解为将mutex--(或-1)
int pthread_mutex_lock(pthread_mutex_t *mutex);
#include <func.h>
int main()
{
pthread_mutex_t mutex;
int ret;
ret=pthread_mutex_init(&mutex,NULL);
THREAD_ERROR_CHECK(ret,"pthread_mutex_init");
pthread_mutex_lock(&mutex);
pthread_mutex_lock(&mutex);//会阻塞
printf("you can't see me\n");
ret=pthread_mutex_destroy(&mutex);
THREAD_ERROR_CHECK(ret,"pthread_mutex_destroy");
return 0;
}
pthread_mutex_unlock函数
解锁。可理解为将mutex ++(或+1)
int pthread_mutex_unlock(pthread_mutex_t *mutex);
主线程与子线程实现对同一变量的加法运算:
#include <func.h>
//主线程与子线程各加1000万
#define N 10000000
typedef struct{
int num;
pthread_mutex_t mutex;
}Data;
void* threadFunc(void *p)
{
Data *pThreadInfo=(Data *)p;
int i;
for(i=0;i<N;i++)
{
pthread_mutex_lock(&pThreadInfo->mutex);
pThreadInfo->num+=1;
pthread_mutex_unlock(&pThreadInfo->mutex);
}
printf("I am child thread\n");
return NULL;
}
int main()
{
pthread_t pthID;//线程ID
int ret;
Data threadInfo;
threadInfo.num=0;
ret=pthread_mutex_init(&threadInfo.mutex,NULL);
THREAD_ERROR_CHECK(ret,"pthread_mutex_init");
struct timeval start,end;
gettimeofday(&start,NULL);
ret=pthread_create(&pthID,NULL,threadFunc,&threadInfo);
if(ret!=0)
{
printf("pthread_create:%s\n",strerror(ret));
return -1;
}
int i;
for(i=0;i<N;i++)
{
pthread_mutex_lock(&threadInfo.mutex);
threadInfo.num+=1;
pthread_mutex_unlock(&threadInfo.mutex);
}
pthread_join(pthID,NULL);
gettimeofday(&end,NULL);
ret=pthread_mutex_destroy(&threadInfo.mutex);
THREAD_ERROR_CHECK(ret,"pthread_mutex_destroy");
printf("I am main thread,%d,use time=%ld\n",threadInfo.num,(end.tv_sec-start.tv_sec)*1000000+end.tv_usec-start.tv_usec);
return 0;
}
pthread_mutex_trylock函数
尝试加锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);
#include <func.h>
//trylock尝试加锁
int main()
{
pthread_mutex_t mutex;
int ret;
ret=pthread_mutex_init(&mutex,NULL);
THREAD_ERROR_CHECK(ret,"pthread_mutex_init");
pthread_mutex_lock(&mutex);
ret=pthread_mutex_trylock(&mutex);
THREAD_ERROR_CHECK(ret,"pthread_mutex_trylock");
ret=pthread_mutex_destroy(&mutex);
THREAD_ERROR_CHECK(ret,"pthread_mutex_destroy");
return 0;
}
线程加锁后被取消
线程取消的方法是一个线程向目标线程发cancel 信号,但是如何处理cancel 信号则由目标线程自己决
定,目标线程或者忽略、或者立即终止、或者继续运行至cancelation-point(取消点)后终止。
线程为了访问临界共享资源而为其加上锁,但在访问过程中该线程被外界取消,或者发生了中断,则该
临界资源将永远处于锁定状态得不到释放。外界取消操作是不可预见的,因此的确需要一个机制来简化用于
资源释放的编程。
在POSIX 线程API 中提供了一个pthread_cleanup_push()/pthread_cleanup_pop()函数对用于自动释
放资源,从pthread_cleanup_push()的调用点到pthread_cleanup_pop()之间的程序段中的终止动作都将执行
pthread_cleanup_push()所指定的清理函数。
函数原型:
void pthread_cleanup_push(void (*routine) (void *), void *arg)
void pthread_cleanup_pop(int execute)
pthread_cleanup_push()/pthread_cleanup_pop()
采用先入后出的栈结构管理,并且必须成对出现!
void routine(void *arg)
函数在调用pthread_cleanup_push()
时压入清理函数栈,多次对
pthread_cleanup_push()
的调用将在清理函数栈中形成一个函数链,在执行该函数链时按照压栈的相反
顺序弹出。execute 参数表示执行到pthread_cleanup_pop()
时是否在弹出清理函数的同时执行该函数,
为0 表示不执行,非0 为执行;这个参数并不影响异常终止时清理函数的执行。
#include <func.h>
void cleanup(void *p)
{
pthread_mutex_unlock((pthread_mutex_t*)p);
printf("unlock success\n");
}
//线程加锁以后被cancel怎么办
void* threadFunc(void *p)
{
pthread_mutex_t* pMutex=(pthread_mutex_t*)p;
pthread_cleanup_push(cleanup,pMutex); //指定清理函数cleanup
pthread_mutex_lock(pMutex);
sleep(3);
pthread_cleanup_pop(1);
pthread_exit(NULL);
}
int main()
{
pthread_mutex_t mutex;
int ret;
ret=pthread_mutex_init(&mutex,NULL);
THREAD_ERROR_CHECK(ret,"pthread_mutex_init");
pthread_t pthId[2];
int i;
for(i=0;i<2;i++)
{
pthread_create(pthId+i,NULL,threadFunc,&mutex);
}
for(i=0;i<2;i++)
{
ret=pthread_cancel(pthId[i]);
THREAD_ERROR_CHECK(ret,"pthread_cancel");
}
for(i=0;i<2;i++)
{
pthread_join(pthId[i],NULL);
}
ret=pthread_mutex_destroy(&mutex);
THREAD_ERROR_CHECK(ret,"pthread_mutex_destroy");
return 0;
}
多线程售票功能(互斥锁)
#include <func.h>
typedef struct{
int tickets;
pthread_mutex_t mutex;
}Train;
void* saleWindows1(void* p)
{
Train *pSale=(Train*)p;
int count=0;
while(1)
{
pthread_mutex_lock(&pSale->mutex);
if(pSale->tickets>0)
{
//printf("I am saleWindows1,the tickets is %d\n",pSale->tickets);
pSale->tickets--;
count++;
//printf("sale finish,I am saleWindows1,the tickets is %d\n",pSale->tickets);
pthread_mutex_unlock(&pSale->mutex);
}else{
pthread_mutex_unlock(&pSale->mutex);
printf("I am windows1 sale %d\n",count);
break;
}
}
return NULL;
}
void* saleWindows2(void* p)
{
Train *pSale=(Train*)p;
int count=0;
while(1)
{
pthread_mutex_lock(&pSale->mutex);
if(pSale->tickets>0)
{
//printf("I am saleWindows2,the tickets is %d\n",pSale->tickets);
pSale->tickets--;
count++;
//printf("sale finish,I am saleWindows2,the tickets is %d\n",pSale->tickets);
pthread_mutex_unlock(&pSale->mutex);
}else{
pthread_mutex_unlock(&pSale->mutex);
printf("I am windows2 sale %d\n",count);
break;
break;
}
}
return NULL;
}
typedef void* (*threadFunc)(void*);
int main()
{
Train t;
pthread_t pthId[2];
int i;
t.tickets=20000000;
threadFunc pthreadFunc[2]={saleWindows1,saleWindows2};
pthread_mutex_init(&t.mutex,NULL);
for(i=0;i<2;i++)
{
pthread_create(pthId+i,NULL,pthreadFunc[i],&t);
}
for(i=0;i<2;i++)
{
pthread_join(pthId[i],NULL);
}
printf("sale over\n");
}
条件变量
条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待条件变
量的条件成立而挂起;另一个线程使条件成立(给出条件成立信号)。为了防止竞争,条件变量的使用总是
和一个互斥锁结合在一起。
pthread_cond_init函数
pthread_cond_destroy函数
pthread_cond_wait函数
pthread_cond_timedwait函数
pthread_cond_signal函数
pthread_cond_broadcast函数
以上6 个函数的返回值都是:成功返回0, 失败直接返回错误号。
pthread_cond_t类型 用于定义条件变量
pthread_cond_t cond;
pthread_cond_init函数
初始化一个条件变量
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
参2:attr表条件变量属性,通常为默认值,传NULL即可
也可以使用静态初始化的方法,初始化条件变量:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_cond_destroy函数
销毁一个条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
pthread_cond_wait函数(※)
阻塞等待一个条件变量
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
函数作用:
-
阻塞等待条件变量cond(参1)满足
-
释放已掌握的互斥锁(解锁互斥量)相当于pthread_mutex_unlock(&mutex);
1.2.两步为一个原子操作。
- 当被唤醒,pthread_cond_wait函数返回时,解除阻塞并重新申请获取互斥锁pthread_mutex_lock(&mutex);
先举一个简单的例子:
#include <func.h>
typedef struct{
pthread_cond_t cond;
pthread_mutex_t mutex;
}Data;
void* threadFunc(void* p)
{
Data* pthreadInfo=(Data*)p;
int ret;
pthread_mutex_lock(&pthreadInfo->mutex);
ret=pthread_cond_wait(&pthreadInfo->cond,&pthreadInfo->mutex);
pthread_mutex_unlock(&pthreadInfo->mutex);
printf("I am child,after wait\n");
pthread_exit(NULL);
}
int main()
{
Data threadInfo;
int ret;
ret=pthread_cond_init(&threadInfo.cond,NULL);
THREAD_ERROR_CHECK(ret,"pthread_cond_init");
pthread_mutex_init(&threadInfo.mutex,NULL);
pthread_t pthId;
pthread_create(&pthId,NULL,threadFunc,&threadInfo);
sleep(1);
ret=pthread_cond_signal(&threadInfo.cond);
THREAD_ERROR_CHECK(ret,"pthread_cond_signal");
printf("send signal ok\n");
pthread_join(pthId,NULL);
printf("I am main thread\n");
return 0;
}
代码解释:主线程创建结构体,包含mutex和cond,初始化后创建子线程,子线程上锁,并执行pthread_cond_wait阻塞等待条件变量的到来,sleep(1)后主线程发送signal信号使条件变量成立,故打印出 send signal ok和I am child,after wait,最后用join回收子线程。
pthread_cond_timedwait函数
功能:计时等待一个条件变量。线程解开mutex 指向的锁并被条件变量cond 阻塞。其中计时等待方式表示经历abstime 段时间后,即使条件变量不满足,阻塞也被解除。
函数原型:
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
参3: 参看man sem_timedwait函数,查看struct timespec结构体。
struct timespec {
time_t tv_sec; /* seconds */ 秒
long tv_nsec; /* nanosecondes*/ 纳秒
}
形参abstime:绝对时间。
如:time(NULL)返回的就是绝对时间。而alarm(1)是相对时间,相对当前时间定时1秒钟。
struct timespec t = {1, 0};
pthread_cond_timedwait (&cond, &mutex, &t);
只能定时到 1970年1月1日 00:00:01秒(早已经过去)
正确用法:
time_t cur = time(NULL); //获取当前时间。
struct timespec t; //定义timespec 结构体变量t
t.tv_sec = cur+1; //定时1秒
pthread_cond_timedwait (&cond, &mutex, &t); //传参
struct timeval {
time_t tv_sec; /* seconds * 秒/
suseconds_t tv_usec; /* microseconds * 微秒/
};
示例:
#include <func.h>
typedef struct{
pthread_cond_t cond;
pthread_mutex_t mutex;
}Data;
void* threadFunc(void* p)
{
Data* pthreadInfo=(Data*)p;
int ret;
struct timespec t;
t.tv_nsec=0;
t.tv_sec=time(NULL)+5;
pthread_mutex_lock(&pthreadInfo->mutex);
ret=pthread_cond_timedwait(&pthreadInfo->cond,&pthreadInfo->mutex,&t);
printf("pthread_cond_timedwait ret=%d\n",ret);
pthread_mutex_unlock(&pthreadInfo->mutex);
printf("I am child,after wait\n");
pthread_exit(NULL);
}
int main()
{
Data threadInfo;
int ret;
ret=pthread_cond_init(&threadInfo.cond,NULL);
THREAD_ERROR_CHECK(ret,"pthread_cond_init");
pthread_mutex_init(&threadInfo.mutex,NULL);
pthread_t pthId;
pthread_create(&pthId,NULL,threadFunc,&threadInfo);
sleep(10);
ret=pthread_cond_signal(&threadInfo.cond);
THREAD_ERROR_CHECK(ret,"pthread_cond_signal");
printf("send signal ok\n");
pthread_join(pthId,NULL);
printf("I am main thread\n");
return 0;
}
代码解释:主线程控制10s后发signal但子线程设置5秒计时,超时则自动解锁,故程序启动后过了5秒后打印I am child,after wait,再过5秒后打印剩余部分!
pthread_cond_signal函数
唤醒至少一个阻塞在条件变量上的线程
int pthread_cond_signal(pthread_cond_t *cond);
pthread_cond_broadcast函数
唤醒全部阻塞在条件变量上的线程
int pthread_cond_broadcast(pthread_cond_t *cond);
条件变量的优点
相较于mutex而言,条件变量可以减少竞争。
如直接使用mutex,除了生产者、消费者之间要竞争互斥量以外,消费者之间也需要竞争互斥量,但如
果汇聚(链表)中没有数据,消费者之间竞争互斥锁是无意义的。有了条件变量机制以后,只有生产者完成
生产,才会引起消费者之间的竞争。提高了程序效率。
生产者消费者模型
通过设置条件变量,在售票为空后自动补票。
#include <func.h>
typedef struct{
int tickets;
pthread_mutex_t mutex;
pthread_cond_t cond;
}Train;
void* saleWindows1(void* p)
{
Train *pSale=(Train*)p;
int count=0;
while(1)
{
pthread_mutex_lock(&pSale->mutex);
if(pSale->tickets>0)
{
printf("I am saleWindows1,the tickets is %d\n",pSale->tickets);
pSale->tickets--;
if(0==pSale->tickets)
{
pthread_cond_signal(&pSale->cond);
}
count++;
printf("sale finish,I am saleWindows1,the tickets is %d\n",pSale->tickets);
pthread_mutex_unlock(&pSale->mutex);
}else{
pthread_mutex_unlock(&pSale->mutex);
printf("I am windows1 sale %d\n",count);
break;
}
sleep(1);
}
return NULL;
}
void* saleWindows2(void* p)
{
Train *pSale=(Train*)p;
int count=0;
while(1)
{
pthread_mutex_lock(&pSale->mutex);
if(pSale->tickets>0)
{
printf("I am saleWindows2,the tickets is %d\n",pSale->tickets);
pSale->tickets--;
if(0==pSale->tickets)
{
pthread_cond_signal(&pSale->cond);
}
count++;
printf("sale finish,I am saleWindows2,the tickets is %d\n",pSale->tickets);
pthread_mutex_unlock(&pSale->mutex);
}else{
pthread_mutex_unlock(&pSale->mutex);
printf("I am windows2 sale %d\n",count);
break;
}
sleep(1);
}
return NULL;
}
void* setTickets(void* p)
{
Train *pSale=(Train*)p;
pthread_mutex_lock(&pSale->mutex);
if(pSale->tickets>0)
{
pthread_cond_wait(&pSale->cond,&pSale->mutex);
}
pSale->tickets=20;
pthread_mutex_unlock(&pSale->mutex);
return NULL;
}
typedef void* (*threadFunc)(void*);
#define N 3
int main()
{
Train t;
pthread_t pthId[N];
int i;
t.tickets=20;
threadFunc pthreadFunc[N]={saleWindows1,saleWindows2,setTickets};
pthread_mutex_init(&t.mutex,NULL);
pthread_cond_init(&t.cond,NULL);
for(i=0;i<N;i++)
{
pthread_create(pthId+i,NULL,pthreadFunc[i],&t);
}
for(i=0;i<N;i++)
{
pthread_join(pthId[i],NULL);
}
printf("sale over\n");
}