进程是系统中程序执行的资源分配的基本单位。每个进程都有自己的数据段、代码段和堆栈段。这就造成了进程切换等操作时,需要上下文切换,保存现场等动作。为了减少cpu空转时间和减少上下文切换开销,就出现了线程的概念。
线程是共享内存空间中并行执行的躲到执行路径,拥有独立的执行序列,是基本的调度单位,每一个进程都至少有一个main主线程。线程与同进程中的其他线程共享进程空间(堆区、代码区、数据区、文件描述符等),只拥有自己的栈空间,因此在切换的时候只需要考虑栈地址空间,大大减少了进程内线程切换的开销。
线程和进程在使用上各有优缺点:线程执行开销小,占用的CPU少,线程之间切换快,但不利于资源的管理和保护;进程刚好相反,而且多进程的可移植性要更好一些。
和进程一样,线程也有一个类似于PCB(进程控制块)的TCB(线程控制块)。一个进程可以有多个线程,也就是说有多个TCB和堆栈寄存器,但是这些线程都共享一个用户地址空间。注意的是:由于线程共享进程的资源和地址空间,因此,任何线程对系统资源的操作都会给其他线程带来影响。
一般情况下线程分为用户级线程和内核级线程。
Linux的线程是通过用户级的函数库实现的,一般采用pthread线程库实现线程的访问和控制。使用第三方Posix标准的pthread,具有良好的可移植性。
创建线程实际上就是确定调用该线程函数的入口点,使用pthread_create创建。在线程创建以后,就开始运行相关的线程函数,在该函数运行完后,线程自动退出,这是线程退出的一种方式。另一种线程主动退出的函数是pthread_exit。注意在使用线程函数式,不能随意使用exit退出函数进行出错处理,因为exit会导致当前进程直接退出,应该使用pthread_exit代替exit函数。
由于一个进程中的多个线程是共享数据段,因此线程退出后,该线程占用的资源不会随之释放。如进程的wait函数系统调用同步终止并释放资源,线程也有一个pthread_join 函数,该函数可以将当前的进程挂起,等待进程结束。该函数是一个线程阻塞函数,嗲用它的函数将一直等待知道被等待的进程结束为止,当函数返回时,被等待线程的资源被回收。
//线程的头文件
#include
函数int pthread_create(pthread_t* thread, pthread_attr_t * attr, void *(*start_routine)(void *), void * arg);
用来创建线程,成功返回0,失败返回对应的错误码。参数描述如下:
函数void pthread_exit(void *retval);
表示线程的退出。参数可以被其他线程用pthread_join捕获。
示例 2-1:创建线程并且退出
#include
#include
#include
void* ThreadFunc(void *pArg)
{for(int i = 0;i<10;i++){printf("我是子线程,参数是%ld\n",(long)pArg);sleep(1);}pthread_exit(NULL);
}
int main()
{pthread_t thid;pthread_create(&thid,NULL,ThreadFunc,(void*)123);for(int i = 0;i<10;i++){printf("我是主线程,子线程是%x\n",thid);sleep(2);}return 0;
}
结果分析: 如果不在主线程中添加sleep(2),子线程无法运行所有的for循环,在主线程退出后就会退出,读者可以自行尝试。编译文件时注意要带上线程库,即在编译后加上 -lpthread 。
线程从入口函数自然返回,或者调用函数pthread_exit都可以线程正常终止,函数的返回值可以被其他线程用pthread_join函数获取
#include
int pthread_join(pthread_t thid, void **thread_return);
示例 3-1-1:获取线程返回值
#include
#include
#include //线程函数
void *ThreadFunc(void *pArg)
{long iArg = (long)pArg; //将 void*转换为 intsleep(iArg); //传入参数为休眠时间if(iArg < 3)return (void *)(iArg*2); //有返回值elsepthread_exit((void *)(iArg*2)); //和 return 达到的效果一样,都可以用于正常返回
}
int main()
{pthread_t thid;long iRet = 0;pthread_create(&thid,NULL,ThreadFunc,(void*)2); //创建线程并且传入参数2pthread_join(thid,(void**)&iRet); //接收子线程的返回值printf("第一个子线程返回值:%ld\n",iRet);pthread_create(&thid,NULL,ThreadFunc,(void*)4);pthread_join(thid,(void**)&iRet);printf("第二个子线程返回值:%ld\n",iRet);return 0;
}
示例 3-1-2:子线程释放空间
pthread_join函数还有一个非常重要的作用,由于一个进程中多个线程共享数据段,因此通常在一个线程退出后,退出线程所占用的资源并不会随线程结束而释放。如果th线程类型不会自动清理资源,则th线程结束后,线程本身的资源必须通过其它线程调用pthread_join来清除,这相当于多进程中的waitpid() 函数。
#include
#include
#include
#includevoid* threadfunc(void *args)
{char* p = (char*)malloc(10); //自己分配了10个内存空间for(int i = 1;i <= 10;i++){printf("子线程运行%d\n",i);sleep(1);}//子线程手动释放空间free(p);printf("子线程释放全部空间\n");pthread_exit((void*)3);
}
int main()
{pthread_t pthid;pthread_create(&pthid,NULL,threadfunc,NULL);for(int i = 1;i <= 5;i++){ //父线程的运行次数比子线程要少,当父线程结束时,如果没有pthread_join函数等待子线程结束的话,子线程也会退出printf("父线程运行%d\n",i);sleep(1);//if(i%3 == 0)// pthread_cancel(pthid);//表示当i%3 == 0的时候就取消了子线程,该函数将导致子线程直接退出,不会执行上free代码,子进程自动释放空间失败//此时必须使用pthread_cleanup_push和pthread_cleanup_pop函数释放空间 }long retvalue = 0;pthread_join(pthid,(void**)&retvalue); //等待子线程释放空间printf("等待子线程结束,子线程返回值:%ld\n",retvalue);return 0;
}
注意: pthread_join不会回收堆内存,只回收线程的栈内存和内核中的struct task_struct结构体占用的内存。
线程可以被其他线程杀掉,在Linux中的说法是,一个线程可以被另一个线程取消(cancel)。
线程取消的方法是一个线程向目标线程发送cansel信号,但是如何处理cancel信号则由目标线程自己决定,目标线程或者忽略、或者终止、或者继续运行值cancelation-point(取消点)后终止。
取消点:
根据 POSIX 标准,pthread_join()、pthread_testcancel()、pthread_cond_wait()、pthread_cond_timedwait()、sem_wait()、sigwait()等函数以及 read()、write()等会引起阻塞的系统调用都是 Cancelation-point,而其他 pthread 函数都不会引起 Cancelation 动作。但是 pthread_cancel 的手册页声称,由于 Linux 线程库与 C 库结合得不好,因而目前 C 库函数都不是 Cancelation-point;但 CANCEL信号会使线程从阻塞的系统调用中退出,并置 EINTR 错误码,因此可以在需要作为 Cancelation-point的系统调用前后调用 pthread_testcancel(),从而达到 POSIX 标准所要求的目标,即如下代码段:
pthread_testcancel();
retcode = read(fd, buffer, length);
pthread_testcancel();
但是从 RedHat9.0 的实际测试来看,至少有些 C 库函数的阻塞函数是取消点,如 read(),getchar()等,而 sleep()函数不管线程是否设置了 pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL),都起到取消点作用。总之,线程的取消一方面是一个线程强行杀另外一个线程,从程序设计角度看并不是一种好的风格,另一方面目前 Linux 本身对这方面的支持并不完善,所以在实际应用中应该谨慎使用!!
int pthread_cancel(pthread_t thread);
需要增加 man 信息 ,apt-get install manpages-posix-de
不论是可预见的线程终止还是异常终止,都会存在资源释放的问题,在不考虑因运行出错而退出的前提下,如何保证线程终止时能够顺利的释放掉自己所占有的资源,特别是锁资源。
经常会出现的情况,是资源独占锁的使用,线程为了访问临界资源给其上锁,但是访问过程中线程退出了,或者中断了,该临界资源将永远处于锁定状态得不到释放。外界取消操作者是不可预见的,因此需要释放资源。
在 POSIX 线程 API 中提供了一个 pthread_cleanup_push()/pthread_cleanup_pop()函数对用于自动释放资源–从pthread_cleanup_push()的调用点到 pthread_cleanup_pop()之间的程序段中的终止动作都将执行 pthread_cleanup_push()所指定的清理函数。API 定义如下:
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 为执行;这个参数并不影响异常终止时清理函数的执行。
pthread_cleanup_push()/pthread_cleanup_pop()是以宏定义方式实现的。
#define pthread_cleanup_push(routine,arg)
{struct _pthread_cleanup_buffer _buffer; \_pthread_cleanup_push (&_buffer, (routine), (arg));#define pthread_cleanup_pop(execute) \_pthread_cleanup_pop (&_buffer, (execute));
}
pthread_cleanup_push()和pthread_cleanup_pop()分别带有"{" 和 “}”,因此这两个函数必须成对出现,且必须位于程序的同一级别的代码段中才能编译。
pthread_cleanup_pop 的参数 execute 如果为非 0 值,则按栈的顺序注销掉一个原来注册的清理函数,并执行该函数;当 pthread_cleanup_pop()函数的参数为 0 时,仅仅在线程调用 pthread_exit 函数或者其它线程对本线程调用 pthread_cancel 函数时,才在弹出“清理函数”的同时执行该“清理函数”。
示例 3-3-1:
#include
#includevoid CleanFunc(void* pArg)
{printf("这是线程清理函数%d\n",(long)pArg);
}
void* ThreadFunc(void* pArg)
{pthread_cleanup_push(CleanFunc,(void*)1);pthread_cleanup_push(CleanFunc,(void*)2);sleep(2);pthread_cleanup_pop(1);pthread_cleanup_pop(200); //参数非0即可
}
int main()
{pthread_t thid;pthread_create(&thid,NULL,Threadfunc,(void*)2);pthread_join(thid,NULL);return 0;
}
结果: 由于入栈顺序是1,2,所以弹出顺序是2,1。
考虑其他情况: 如果将pthread_cleanup参数改为0,就不会有任何输出,CleanFunc不会得到运行。如果在sleep(2)后再添加pthread_exit(NULL); 就和原结果一样了。
示例 3-3-2: 用 pthread_cleanup_push 和 pthread_cleanup_pop 来释放子线程分配的内存空间
#include
#include
#include
#include
//清理内存函数
void freemem(void* args)
{free(args);printf("清理子线程申请的空间\n");
}
//子线程指针函数
void* threadfunc(void* args)
{char* p = (char*)malloc(10); //申请10个空间pthread_cleanup_push(freemem,p); //将清理函数地址压入函数栈,将开辟的空间地址作为参数传入,并且一并压栈for(int i = 1;i <= 10;i++){printf("子线程执行%d次\n",i);sleep(1);}pthread_exit((void*)3); //线程退出,并且传入参数3,可以被pthread_join捕捉pthread_cleanup_pop(0); //参数为0没有效果,和pthread_cleanup_push成对
}
int main()
{pthread_t pthid; //线程标识符pthread_create(&pthid,NULL,threadfunc,NULL);for(int i = 1;i <= 5;i++){//父线程运行次数比子线程的少,当父线程结束时,如果没有pthread_join函数等待子线程执行的haul,子线程也会退出,即使没有执行完printf("父线程执行%d次\n",i);sleep(2);if(i%2 ==0){ //满足条件取消子线程,导致子线程来不及free空间,就需要用到pthread_cleanup_push和pthread_cleanup_pop//因为pthread_cleanup_pop参数为0,所以调用pthread_cancel时,会将栈中的函数弹出并执行,即释放空间pthread_cancel(pthid); }}long retvalue = 0;pthread_join(pthid,(void**)&retvalue); //等待子线程释放空间,并获取子线程的返回值printf("子线程返回值是%ld\n",retvalue); //如果为-1,说明子线程已关闭return 0;
}
在 Posix Thread 中定义了一套专门用于线程互斥的 mutex 函数。mutex 是一种简单的加锁的方法来控制对共享资源的存取,这个互斥锁只有两种状态(上锁和解锁),可以把互斥锁看作某种意义上的全局变量。为什么需要加锁,就是因为多个线程共用进程的资源,要访问的是公共区间时(全局变量),当一个线程访问的时候,需要加上锁以防止另外的线程对它进行访问,实现资源的独占。在一个时刻只能有一个线程掌握某个互斥锁,拥有上锁状态的线程能够对共享资源进行操作。若其他线程希望上锁一个已经上锁了的互斥锁,则该线程就会挂起,直到上锁的线程释放掉互斥锁为止。
POSIX 定义了一个宏 PTHREAD_MUTEX_INITIALIZER 来静态初始化互斥锁,方法如下:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
在 Linux Threads 实现中,pthread_mutex_t 是一个结构,而 PTHREAD_MUTEX_INITIALIZER 则是
一个宏常量。
动态方式是采用 pthread_mutex_init()函数来初始化互斥锁,API 定义如下:
#include
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);
其中 mutexattr 用于指定互斥锁属性(见下),如果为 NULL 则使用缺省属性。通常为 NULL
int pthread_mutex_destroy(pthread_mutex_t *mutex);
销毁一个互斥锁意味着释放它所占有的资源,且要求锁当前处于开放状态。由于在Linux中,互斥锁并不占有任何资源,因此 Linux Threads 中的 pthread_mutex_destroy()除了检查锁状态以外(锁定状态则返回 EBUSY)没有其他动作。
互斥锁的属性在创建锁的时候设定,不同的锁类型在试图对一个已经被锁定的互斥锁加锁时表现不同,也就是说是否阻塞等待。有三种属性可以选择:
示例:初始化锁
pthread_mutex_t lock;
pthread_mutex_init(&lock,NULL);
示例:初始化一个嵌套锁
pthread_mutex_t lock;
pthread_mutexattr mutexattr;
pthread_mutexattr_init(&mutexattr);
pthread_mutexattr_settype(&mutexattr,PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&lock,&mutexattr);
示例:初始化一个检错锁
pthread_mutex_t lock;
pthread_mutexattr_t mutexattr;
pthread_mutexattr_settype(&mutexattr, PTHREAD_MUTEX_ERRORCHECK);
pthread_mutex_init(&lock, &mutexattr);
操作 | 函数 |
---|---|
加锁 | int pthread_mutex_lock(pthread_mutex_t *mutex) |
解锁 | int pthread_mutex_unlock(pthread_mutex_t *mutex) |
测试加锁 | int pthread_mutex_trylock(pthread_mutex_t *mutex) |
示例 4-1-1:比较 pthread_mutex_trylock()与 pthread_mutex_lock()
#include#includepthread_mutex_t lock;void* pthfunc(void* args)
{pthread_mutex_lock(&lock); //先加一锁pthread_mutex_lock(&lock); //再加一次锁,会挂起阻塞//pthread_mutex_trylock(&lock); //用trylock加锁,不会挂起阻塞printf("test\n");sleep(1);pthread_exit(NULL);
}
int main()
{pthread_t pthid = 0;pthread_mutex_init(&lock,NULL); //初始化锁pthread_create(&pthid,NULL,pthfunc,NULL); //注册捕捉函数pthread_join(pthid,NULL); //等待线程消亡pthread_mutex_destroy(&lock); //销毁锁return 0;
}
死锁产生的原因
系统资源的竞争
系统资源的竞争导致系统资源不足,以及资源分配不当,导致死锁。
进程运行推进顺序不合适
进程在运行过程中,请求和释放资源的顺序不当,会导致死锁。
死锁的四个必要条件
死锁的预防
我们可以通过破坏死锁产生的 4 个必要条件来 预防死锁,由于资源互斥是资源使用的固有特性是无法改变的。
破坏“不可剥夺”条件:一个进程不能获得所需要的全部资源时便处于等待状态,等待期间他占有的资源将被隐式的释放重新加入到 系统的资源列表中,可以被其他的进程使用,而等待的进程只有重新获得自己原有的资源以及新申请的资源才可以重新启动,执行。
破坏”请求与保持条件“:第一种方法静态分配即每个进程在开始执行时就申请他所需要的全部资源。第二种是动态分配即每个进程在申请所需要的资源时他本身不占用系统资源。
破坏“循环等待”条件:采用资源有序分配其基本思想是将系统中的所有资源顺序编号,将紧缺的采用较大的编号,在申请资源时必须按照编号的顺序进行,一个进程只有获得较小编号的进程才能申请较大编号的进程。
互斥锁示例:火车站买票(不加锁,就会出现负票数
#include
#include
#include
int ticketcount = 10; // 火车票,公共资源(全局)
void* salewinds(void* args)
{while(ticketcount>0){ //如果有票,则卖票sleep(1); //1秒卖一张票ticketcount--; //出票成功printf("窗口一:还剩%d张票\n",ticketcount);}
}
void* salewinds2(void* args)
{while(ticketcount>0){ //如果有票,则卖票sleep(1); //1秒卖一张票ticketcount--; //出票成功printf("窗口二:还剩%d张票\n",ticketcount);}
}
int main()
{pthread_t pthid1 = 0;pthread_t pthid2 = 0;pthread_create(&pthid1,NULL,salewinds,NULL); //线程 1创建pthread_create(&pthid2,NULL,salewinds2,NULL); //线程 2创建pthread_join(pthid1,NULL); //等待线程结束pthread_join(pthid2,NULL); //等待线程结束return 0;
}
互斥锁示例:火车站买票(加锁,就不会出现负票数,而且可以实现交替卖票
#include
#include
#include
int ticketcount = 10; // 火车票,公共资源(全局)
pthread_mutex_t lock; // 定义线程锁
void* salewinds(void* args)
{while(1){pthread_mutex_lock(&lock); //访问全局变量(资源),先加锁if(ticketcount>0){ //如果有票,则卖票sleep(1); //1秒卖一张票ticketcount--; //出票成功printf("窗口一:还剩%d张票\n",ticketcount);}else{ //如果没有票就解锁退出pthread_mutex_unlock(&lock); //解锁pthread_exit(NULL); //退出线程}pthread_mutex_unlock(&lock); //每卖一张票就要准备一下,解锁sleep(1); //让另一个窗口有时间卖票,否则资源成为独享资源了}
}
void* salewinds2(void* args)
{while(1){pthread_mutex_lock(&lock); //访问全局变量(资源),先加锁if(ticketcount>0){ //如果有票,则卖票sleep(1); //1秒卖一张票ticketcount--; //出票成功printf("窗口二:还剩%d张票\n",ticketcount);}else{ //如果没有票就解锁退出pthread_mutex_unlock(&lock); //解锁pthread_exit(NULL); //退出线程}pthread_mutex_unlock(&lock); //每卖一张票就要准备一下,解锁sleep(1); //让另一个窗口有时间卖票,否则资源成为独享资源了}
}
int main()
{pthread_t pthid1 = 0;pthread_t pthid2 = 0;pthread_mutex_init(&lock,NULL); //初始化锁pthread_create(&pthid1,NULL,salewinds,NULL); //线程 1创建pthread_create(&pthid2,NULL,salewinds2,NULL); //线程 2创建pthread_join(pthid1,NULL); //等待线程结束pthread_join(pthid2,NULL); //等待线程结束pthread_mutex_destroy(&lock); //销毁锁return 0;
}
总结:线程互斥mutex加锁步骤
写在前面,作者在整理这一块学习资料的时候,不明白为什么有了互斥锁还要用条件变量,所以参考了网上的一些资料:
互斥锁体现的是一种竞争,即互斥关系,我离开了,通知你进来。
条件变量体现的是一种协作,即同步关系,我准备好了,你可以开始了。
互斥锁缺点是只有两个状态,锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起配合使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其他的某个线程改变了条件变量,他将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。
条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待条件变量的条件成立而挂起;另一个线程使条件成立(给出条件成立信号)。为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。
静态方式使 PTHREAD_COND_INITIALIZER 常量,如下:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
动态方式调用 pthread_cond_init()函数,API 定义如下:
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
尽管 POSIX 标准中为条件变量定义了属性,但在 Linux Threads 中没有实现,因此 cond_attr 值通常为 NULL,且被忽略
注销一个条件变量需要调用 pthread_cond_destroy()
,只有在没有线程在该条件变量上等待的时候能注销这个条件变量,否则返回 EBUSY。因为 Linux 实现的条件变量没有分配什么资源,所以注销动作只包括检查是否有等待线程。API 定义如下:int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex,
无论哪种等待方式,都必须和一个互斥锁配合,以防止多个线程同时请求pthread_cond_wait()和pthread_cond_timedwait()的竞争条件。mutex的互斥锁必须是普通锁,且在调用pthread_cond_wait()前必须由本线程加锁,而在更新条件等待队列以前,mutex保持锁定,必须在线程挂起等待前解锁。,条件满足从而离开之前,mutex将被重新加锁,与pthread_cond_wait() 前的加锁动作对应。(也就是说在做 pthread_cond_wait 之前,往往要用pthread_mutex_lock 进行加锁,而调用 pthread_cond_wait 函数会将锁解开,然后将线程挂起阻塞。直到条件被 pthread_cond_signal 激发,再将锁状态恢复为锁定状态,最后再用 pthread_mutex_unlock 进行解锁)
激活的方式有两种形式,pthread_cond_signal()激活一个等待该条件的线程,存在多个等待线程时按入队顺序激活其中一个;而 pthread_cond_broadcast()则激活所有等待线程。
示例 4-2-1:互斥锁和条件变量的结合使用
#include
#include
#includepthread_mutex_t mutex; //定义锁
pthread_cond_t cond; //定义条件变量//线程清理函数:解锁
void ThreadClean(void* arg)
{pthread_mutex_unlock(&mutex);
}void* child1(void* arg)
{//pthread_cleanup_push(ThreadClean,NULL); //将解锁操作压栈while(1){printf("线程1 开始运行\n");printf("线程1 线程锁%d\n",pthread_mutex_lock(&mutex)); //线程1上锁pthread_cond_wait(&cond,&mutex); //无条件等待父进程发送信号printf("线程1 条件已满足\n");pthread_mutex_unlock(&mutex); //解锁sleep(5);}//pthread_cleanup_pop(0); //弹出清理函数但不执行,即不解锁,导致线程2没法上锁return 0;
}
void* child2(void* arg)
{while(1){sleep(3); //使线程1 有时间运行并解锁,让线程2可以请求锁printf("线程2 开始运行\n");printf("线程2 线程锁%d\n",pthread_mutex_lock(&mutex));pthread_cond_wait(&cond,&mutex); //等待父进程发送信号printf("线程2 条件已适用\n");pthread_mutex_unlock(&mutex);sleep(1);}return 0;
}int main()
{pthread_t tid1,tid2;printf("条件变量测试\n");pthread_mutex_init(&mutex,NULL); //初始化锁pthread_cond_init(&cond,NULL); //初始化条件变量pthread_create(&tid1,NULL,child1,NULL); //创建线程1pthread_create(&tid2,NULL,child2,NULL); //创建线程2while(1){sleep(2); //使线程1 有时间运行并解锁,让线程2可以请求锁//pthread_cancel(tid1); //取消线程1,如果不注释可以实现线程12交替 sleep(2);pthread_cond_signal(&cond); //激活等待条件变量的线程}sleep(10);return 0;
}
结果: 线程1和2交替运行
总结: 首先是线程拿到锁,等待条件,解锁。主线程不管理锁,只负责释放条件。线程1和2交替上锁,请求条件,解锁,实现线程1和线程2交替同步运行。读者也可以自行尝试将注释的代码解开,再运行。
条件变量和互斥锁一样,不能用于信号处理,在信号处理函数中调用pthread_cond_signal()或者pthread_cond_broadcast() 很可能产生死锁。
示例 4-2-2:利用互斥锁再次实现买火车票,并且在火车票卖完时通过条件变量重新补5张票
#include
#include
#include
int ticketcount = 5; //票数
pthread_mutex_t lock; //互斥锁
pthread_cond_t cond; //条件变量
//卖票窗口1,线程1
void* salewinds1(void* args)
{while(1){pthread_mutex_lock(&lock); //因为要访问全局的共享变量ticketcount,所以就要加锁if(ticketcount > 0) { //如果有票printf("窗口1开始卖票,剩余票数%d\n",ticketcount);ticketcount--;if(ticketcount == 0)pthread_cond_signal(&cond); //通知没票了,这里可以阻塞线程,不让其退出printf("窗口1卖出一张票,剩余%d张\n",ticketcount);}else{ //如果没票了就解锁退出pthread_mutex_unlock(&lock);break;}pthread_mutex_unlock(&lock);sleep(1);}
}
//卖票窗口2,线程2
void* salewinds2(void* args)
{while(1){pthread_mutex_lock(&lock); //因为要访问全局的共享变量ticketcount,所以就要加锁if(ticketcount > 0) { //如果有票printf("窗口2开始卖票,剩余票数%d\n",ticketcount);ticketcount--;if(ticketcount == 0)pthread_cond_signal(&cond); //通知没票了,这里可以阻塞线程,不让其退出printf("窗口2卖出一张票,剩余%d张\n",ticketcount);}else{ //如果没票了就解锁退出pthread_mutex_unlock(&lock);break;}pthread_mutex_unlock(&lock);sleep(1);}
}
//线程3,重新设置票数
void* setticket(void* args)
{int i = 0; //设置补票次数变量while(i++ < 1){ //pthread_mutex_lock(&lock); //访问全局变量ticketcount,需要加锁if(ticketcount > 0){ //如果有票就解锁并且阻塞,等待窗口发来缺票的条件信号pthread_cond_wait(&cond,&lock); //将锁解开,并且将此线程挂起阻塞,等待发来的条件变量signal信号//如果signal发来了条件变量信号,此时会重新上锁开始补票}ticketcount = 5; //补票printf("补票成功\n");pthread_mutex_unlock(&lock); //解锁sleep(1);}pthread_exit(NULL);
}int main()
{pthread_t pthid1,pthid2,pthid3;pthread_mutex_init(&lock,NULL); //初始化锁pthread_cond_init(&cond,NULL); //初始化条件变量pthread_create(&pthid1,NULL,salewinds1,NULL);pthread_create(&pthid2,NULL,salewinds2,NULL);pthread_create(&pthid3,NULL,setticket,NULL);pthread_join(pthid1,NULL); //等待子线程执行完毕pthread_join(pthid2,NULL); //等待子线程执行完毕pthread_join(pthid3,NULL); //等待子线程执行完毕pthread_mutex_destroy(&lock); //销毁锁pthread_cond_destroy(&cond); //销毁条件变量return 0;
}
#include
#include
#include
#include
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; //定义一个静态锁
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER; //定义一个静态条件变量
struct node{ //定义单链表 int num;struct node *next;
}*head = NUUL; //初始化一个空的头指针//定义释放内存的清理函数
static void cleanup_handler(void* arg)
{printf("清除内存\n");free(arg);pthread_mutex_unlock(&mtx);
}
//定义消费者线程函数
static void* thread_func(void* arg) //消费者
{struct node* p = NULL; pthread_cleanup_push(cleanup_p); //将清理内存函数压栈while(1){pthread_mutex_lock(&mtx); //访问公共head,需要加锁while(head == NULL) {pthread_cond_wait(&cond,&mtx);} //如果此时没有结点,则释放锁挂起等待条件变量p = head; //删除表头结点head = head->next;printf("取出了队头,买到了%d号票\n",p->num);free(p);pthread_mutex_unlock(&mtx);//释放锁}pthread_exit(NULL);pthread_cleanup_pop(0);
}int main()
{pthread_t tid; //设置进程idstruct node* p; //辅助结点pthread_create(&tid,NULL,thread_func,NULL); //创建线程函数for(int i = 0; i < 10;i++){ p = (struct node*)malloc(sizeof(struct node));p->num = i;pthread_mutex_lock(&mtx); //访问公共head,要加锁p->next = head; //头插法head = p;pthread_cond_sign(&cond); //释放pthread_mutex_unlock(&mtx);sleep(1);}printf("生产结束,所以取消消费者线程\n");pthread_cancel(tid);pthread_join(tid,NULL);printf("线程退出\n");return 0;
}
线程安全 是指:如果一个函数能够同时被多个线程调用而得到正确的结果,那么我们说这个函数是线程安全的。线程安全按成的原因,多数是由于全局变量和静态变量的操作不规范。
可重入函数 :如果一个函数只访问自己的局部变量或参数,就称为可重入函数。反之称为,不可重入函数。显然可重入函数是线程安全的。
前面用到的pthread_create函数的第二个参数,创建线程时将其置为NULL,第二个参数attr是一个结构体指针,结构体中的元素时线程的属性,下面就来介绍一下这些属性:
属性设置函数: 成功返回0,失败返回-1
int pthread_attr_init (pthread_attr_t *attr)
,attr传出参数,表示线程属性,后面的线程设置属性函数相似pthread_attr_setscope(pthread_attr_t *attr, init scope
,scope表示绑定或不绑定pthread_attr_setdetachstate(pthread_attr_t *attr, init detachstate);
,detachstate设置线程是否与其他线程分离int pthread_attr_getschedparam(pthread_attr_t *attr, struct sched_param *param);
,param返回线程优先级int pthread_attr_setschedparam(pthread_attr_t *attr, struct sched_param *param);
示例 5-1:线程属性设置函数的使用
#include
#include
#include
#include
#includevoid* threadFunc(void *arg)
{printf("线程函数正在运行,参数是%s\n",(char*)arg);sleep(4);printf("线程结束现在退出。\n");thread_finished = 1;pthread_exit(NULL);
}
char mes[] = "Hello,World.";
int thread_finish = 0;int main()
{int res = 0;pthread_t thid;void* thread_result;pthread_attr_t thread_attr; //定义属性结构体struct sched_param sheduling_value;res = pthread_attr_init(&thread_attr);if(res != 0){perror("属性创建失败\n");exit(EXIT_FAILURE); //宏定义 -1}//设置调度策略res = pthread_attr_setschedupolicy(&thread_attr,SCHED_OTHER);if(res != 0){perror("设置调度策略失败\n");exit(EXIT_FAILURE); //宏定义 -1}//设置脱离状态res = pthread_attr_setdetachstate(&thread_attr,PTHREAD_CREATE_DETACHED);if(res != 0){perror("设置脱离状态失败\n");exit(EXIT_FAILURE); //宏定义 -1}//创建线程res = pthread_create(&thid,&thread_attr,threadFunc,(void*)mes);if(res != 0){perror("线程创建失败\n");exit(EXIT_FAILURE); //宏定义 -1}//获取线程的最大优先级int max_priority = sched_get_priority_max(SCHED_OTHER);//获取线程的最小优先级int min_priority = sched_get_priority_min(SCHED_OTHER);//重新设置优先级别sheduling_value.sched_priority = min_priority + 5);//设置优先级别res = pthread_attr_setscheduparam(&thread_attr,&scheduling_value);if(res != 0){perror("设置优先级失败\n");exit(EXIT_FAILURE); //宏定义 -1}pthread_attr_destroy(&thread_attr);while(!thread_finished){printf("等待进程结束\n");sleep(3);}printf("进程结束了\n");exit(0);return 0;
}