手把手教你Linux下的多线程设计
来源:互联网 发布:毛利兰 黑衣组织 知乎 编辑:程序博客网 时间:2024/05/21 05:39
线程也被称为轻权进程(lightweight process)。
#include <stdio.h>
#include <pthread.h>
void * pthread_func_test(void * arg);
int main()
{
pthread_t pt1,pt2;
pthread_create(&pt1,NULL,pthread_func_test,"This is the Thread_ONE");
pthread_create(&pt2,NULL,pthread_func_test,"This is the Thread_TWO");
sleep(1); //不加上这句,看不到结果。
}
void * pthread_func_test(void * arg)
{
printf("%s /n ",arg);
}
This is the Thread_TWO
const pthread_attr_t *restrict attr,
void *(*start_routine)(void*), void *restrict arg);
当pthread_create调用成功时,该调用返回0;否则,返回一个错误代码指出错误的类型。
欢迎您发邮件与我交流,但因为工作和时间的关系,我有权对您提出的一些问题不予回答,敬请见谅。
接下来,再看另两个重要的函数pthread_exit和pthread_join
函数原型如下:
线程的终止可以是调用了pthread_exit或者该线程的例程结束。也就是说,一个线程可以隐式的退出,也可以显式的调用pthread_exit函数来退出。
pthread_exit函数唯一的参数value_ptr是函数的返回代码,只要pthread_join中的第二个参数value_ptr不是NULL,这个值将被传递给value_ptr。
函数原型如下:
函数pthread_join的作用是,等待一个线程终止。
调用pthread_join的线程将被挂起直到参数thread所代表的线程终止时为止。pthread_join是一个线程阻塞函数,调用它的函数将一直等到被等待的线程结束为止。
如果value_ptr不为NULL,那么线程thread的返回值存储在该指针指向的位置。该返回值可以是由pthread_exit给出的值,或者该线程被取消而返回PTHREAD_CANCELED。
当一个非分离的线程终止后,该线程的内存资源(线程描述符和栈)并不会被释放,直到有线程对它使用了pthread_join时才被释放。因此,必须对每个创建为非分离的线程调用一次pthread_join调用,以避免内存泄漏。否则当线程是可分离的,调用pthread_exit,将终止该调用线程,并释放所有资源,没有线程等待它终止。
至多只能有一个线程等待给定的线程终止。如果已经有一个线程在等待thread线程终止了,那么再次调用pthread_join等待同一线程的线程将返回一个错误。
#include <stdio.h>
#include <pthread.h>
void * pthread_func_test(void * arg);
int main()
{
pthread_t pt1,pt2;
pthread_create(&pt1,NULL,pthread_func_test,"This is the Thread_ONE");
pthread_create(&pt2,NULL,pthread_func_test,"This is the Thread_TWO");
pthread_join(pt1,NULL);
pthread_join(pt2,NULL); //这行不写,会发生什么?或写成pthread_join(pt1,NULL);又会怎么样?
}
void * pthread_func_test(void * arg)
{
printf("%s ",arg);
pthread_exit(NULL); //显式声明
}
到此为止,我们就学习了三个重要的函数--create、exit、join,下一篇,继续讲解多线程编程中的线程互斥问题。
线程互斥
互斥操作,就是对某段代码或某个变量修改的时候只能有一个线程在执行这段代码,其他线程不能同时进入这段代码或同时修改该变量。这个代码或变量称为临界资源。
例如:有两个线程A和B,临界资源为X,首先线程A进入,将X置为加锁状态,在A将锁打开之前的这段时间里,如果此时恰巧线程B也欲获得X,但它发现X处于加锁状态,说明有其它线程正在执行互斥部分,于是,线程B将自身阻塞。。。线程A处理完毕,在退出前,将X解锁,并将其它线程唤醒,于是线程B开始对X进行加锁操作了。通过这种方式,实现了两个不同线程的交替操作。
记住:一个互斥体永远不可能同时属于两个线程。或者处于锁定状态;或者空闲中,不属于任何一个线程。
代码如下:
#include <stdio.h>
#include <pthread.h>
void * pthread_func_test(void * arg);
pthread_mutex_t mu;
int main()
{
int i;
pthread_t pt;
pthread_mutex_init(&mu,NULL); //声明mu使用默认属性,此行可以不写
pthread_create(&pt,NULL,pthread_func_test,NULL);
for(i = 0; i < 3; i++)
{
pthread_mutex_lock(&mu);
printf("主线程ID是:%lu ",pthread_self()); //pthread_self函数作用:获得当前线程的id
pthread_mutex_unlock(&mu);
sleep(1);
}
}
void * pthread_func_test(void * arg)
{
int j;
for(j = 0; j < 3; j++)
{
pthread_mutex_lock(&mu);
printf("新线程ID是:%lu ",pthread_self());
pthread_mutex_unlock(&mu);
sleep(1);
}
}
终端输出结果:
新线程ID是 : 3086490512
主线程ID是 : 3086493376
新线程ID是 : 3086490512
主线程ID是 : 3086493376
新线程ID是 : 3086490512
注:在你机器上运行的结果很可能与这里显示的不一样。
pthread_mutex_lock声明开始用互斥锁上锁,此后的代码直至调用pthread_mutex_unlock为止,都处于加锁状态中,即同一时间只能被一个线程调用执行。当另一个线程执行到pthread_mutex_lock处时,如果该锁此时被其它线程使用,那么该线程被阻塞,即程序将等待到其它线程释放此互斥锁。
上述例子中,涉及到了几个函数:pthread_mutex_init/pthread_mutex_lock/pthread_mutex_unlock/pthread_mutex_destroy/pthread_self
函数原型:
const pthread_mutexattr_t *restrict attr);
函数作用:
初始化互斥体类型变量mutex,变量的属性由attr进行指定。attr设为NULL,即采用默认属性,这种方式与pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER的方式等价。
函数原型:
函数作用:
用来锁住互斥体变量。如果参数mutex所指的互斥体已经被锁住了,那么发出调用的线程将被阻塞直到其他线程对mutex解锁为止。
函数原型:
函数作用:
如果当前的线程拥有参数mutex所指定的互斥体,那么该函数调用将该互斥体解锁。
函数原型:
函数作用:
用来释放互斥体所占用的资源。
函数原型:
函数作用:获得线程自身的ID。前面我们已经提到过,pthread_t的类型为unsigned long int,所以在打印的时候要使用%lu方式,否则将产生奇怪的结果。
但是上面的代码并不完善,假设将循环次数修改得足够的长,打印后的结果可能并不是我们所希望看到的交替打印,可能象下面这样:
新线程ID是 : 3086490512
主线程ID是 : 3086493376
新线程ID是 : 3086490512
新线程ID是 : 3086490512
主线程ID是 : 3086493376
这是什么原因呢?因为Linux是分时操作系统,采用的是时间片轮转的方式,主线程和新线程可能因为其它因素的干扰,获得了非顺序的时间片。如果想要严格的做到“交替”方式,可以略施小计,即加入一个标志。
完整程序如下:
#include <stdio.h>
#include <pthread.h>
void * pthread_func_test(void * arg);
pthread_mutex_t mu;
int flag = 0;
int main()
{
int i;
pthread_t pt;
pthread_mutex_init(&mu,NULL);
pthread_create(&pt,NULL,pthread_func_test,NULL);
for(i = 0; i < 3; i++)
{
pthread_mutex_lock(&mu);
if(flag == 0)
printf("主线程ID是:%lu ",pthread_self());
flag = 1;
pthread_mutex_unlock(&mu);
sleep(1);
}
pthread_join(pt, NULL);
pthread_mutex_destroy(&mu);
}
void * pthread_func_test(void * arg)
{
int j;
for(j = 0; j < 3; j++)
{
pthread_mutex_lock(&mu);
if(flag == 1)
printf("新线程ID是:%lu ",pthread_self());
flag == 0;
pthread_mutex_unlock(&mu);
sleep(1);
}
}
在使用互斥锁的过程中很有可能会出现死锁:即两个线程试图同时占用两个资源,并按不同的次序锁定相应的互斥锁,例如两个线程都需要锁定互斥锁1和互斥锁2,A线程先锁定互斥锁1,B线程先锁定互斥锁2,这时就出现了死锁。此时我们可以使用函数pthread_mutex_trylock,该函数企图锁住一个互斥体,但不阻塞。
函数原型:
函数pthread_mutex_trylock()用来锁住参数mutex所指定的互斥体。如果参数mutex所指的互斥体已经被上锁,该调用不会阻塞等待互斥体的解锁,而会返回一个错误代码。通过对返回代码的判断,程序员就可以针对死锁做出相应的处理。所以在对多个互斥体编程中,尤其要注意这一点。
经过以上的讲解,我们就学习了Linux下关于多线程方面对互斥体变量的操作。下一节,将给大家讲解有关线程同步方面的知识点。
const pthread_condattr_t *restrict attr);
函数说明:
按attr指定的属性初始化cond条件变量。如果attr为NULL,效果等同于pthread_cond_t cond = PTHREAD_COND_INITIALIZER
函数原型:
函数说明:
对所有等待cond这个条件变量的线程解除阻塞。
函数原型:
函数说明:
仅仅解除等待cond这个条件变量的某一个线程的阻塞状态。如果有若干线程挂起等待该条件变量,该调用只唤起一个线程,被唤起的线程是哪一个是不确定的。
函数原型:
pthread_mutex_t *restrict mutex);
函数说明:
该调用自动阻塞发出调用的当前线程,并等待由参数cond指定的条件变量,而且为参数mutex指定的互斥体解锁。被阻塞线程直到有其他线程调用pthread_cond_signal或pthread_cond_broadcast函数置相应的条件变量时,而且获得mutex互斥体才解除阻塞。等待状态下的线程不占用CPU时间。
函数原型:
pthread_mutex_t *restrict mutex,
const struct timespec *restrict abstime);
函数说明:
该函数自动阻塞当前线程等待参数cond指定的条件变量,并为参数mutex指定的互斥体解锁。被阻塞的线程被唤起继续执行的条件是:有其他线程对条件变量cond调用pthread_cond_signal函数;或有其他线程对条件变量cond调用pthread_cond_broadcast;或系统时间到达abstime参数指定的时间;除了前面三个条件中要有一个被满足外,还要求该线程获得参数mutex指定的互斥体。
函数原型:
函数说明:
释放cond条件变量占用的资源。
看下面的示例:
#include <stdio.h>
#include <pthread.h>
pthread_t pt1,pt2;
pthread_mutex_t mu;
pthread_cond_t cond;
int i = 1;
void * decrease(void * arg)
{
while(1)
{
pthread_mutex_lock(&mu);
if(++i)
{
printf("%d ",i);
if(i != 1) printf("Error ");
pthread_cond_broadcast(&cond);
pthread_cond_wait(&cond,&mu);
}
sleep(1);
pthread_mutex_unlock(&mu);
}
}
void * increase(void * arg)
{
while(1)
{
pthread_mutex_lock(&mu);
if(i--)
{
printf("%d ",i);
if(i != 0) printf("Error ");
pthread_cond_broadcast(&cond);
pthread_cond_wait(&cond,&mu);
}
sleep(1);
pthread_mutex_unlock(&mu);
}
}
int main()
{
pthread_create(&pt2,NULL,increase,NULL);
pthread_create(&pt1,NULL,decrease,NULL);
pthread_join(pt1,NULL);
pthread_join(pt2,NULL);
}
以上我们讲解过了Linux下利用pthread.h头文件的多线程编程知识,下一章中,我们继续讲解关于多线程的深入编程知识,敬请期待。
- 手把手教你Linux下的多线程设计
- [原创]手把手教你Linux下的多线程设计--Linux下多线程编程详解(一)
- [原创]手把手教你Linux下的多线程设计--Linux下多线程编程详解(二)
- 【原创】手把手教你Linux下的多线程设计--Linux下多线程编程详解(三)
- 【原创】手把手教你Linux下的多线程设计--Linux下多线程编程详解(四)
- [原创]手把手教你Linux下的多线程设计--Linux下多线程编程详解(一)
- 手把手叫你安装Linux下的questasim(2)
- 手把手教你Linux系统下快速设置NFS
- 手把手教你在Linux下安装LaTeX环境
- 手把手教你设计Community Server的博客皮肤
- 手把手教你设计Community Server的博客皮肤
- 手把手教你如何建立自己的Linux系统
- 手把手教你如何建立自己的Linux系统
- 手把手教你设计SNS社区【1】
- 手把手教你设计SNS社区【2】
- 手把手教你设计SNS社区【3】
- 手把手教你设计SNS社区【4】
- 手把手教你设计SNS社区【5】
- 深度介绍Linux内核是如何工作的
- 费视频教程
- write的奥秘
- vim常用命令
- cuda中设置vs2005和direct3d关联
- 手把手教你Linux下的多线程设计
- 自定义file类型input的样式
- adodc控件问题
- FLEX实践:各种图表的简单应用
- tnsping命令只是测试端口是否通,不检测服务名是否可以解析
- Linux 上实现双向进程间通信管道
- 独家:深度介绍Linux内核是如何工作的
- 数据库小点点
- WACOM安徽