学完线程,真的感觉头大,所以害怕知识掌握不稳固,所以用博客将自己所学捋一捋。
提到线程,我又想到了前几天写的进程,这两个在Linux系统编程中作用举足轻重,两者名字比较像,甚至用法是比较相似的,两者确实有着比较密切的联系的,在学习过程中,我差点把两者搞混。
线程和进程
:
线程是进程内部的划分,它是比进程小的能独立运行的基本单位,线程基本不拥有资源,它与同属一个进程的多线程共享本进程的资源。一个线程可以撤销和创建其他线程,多个线程可以并行执行,系统不停在各个线程之间切换,每个线程只有在系统分配给他的时间片内才取得CPU的控制权。
线程的优点
节约资源,节约时间。由于线程创建相对于进程创建不需要消耗系统资源,所以创建线程花的时间要少于创建进程时花费的时间。而且线程间共享同一进程的数据空间,所以线程间通信可以直接获取相互之间所需要的数据而不需要经过操作系统,这使得线程间的通信更加快捷方便。
线程可以提高应用程序的响应速度在图像界面的程序中,如果有一个非常耗时的操作,它会导致其它操作不能进行而等待这个操作,这时用户操作的速度会变得很慢,多线程环境下,可以将这个操作分配给单独的线程来为完成,这个线程在用完系统分配给他的时间片后,其他操作便有机会执行了。
改善程序的结构,对于要处理多个命令的应用程序,可以将对每个命令的处理设计成一个线程从而避免了设计成大程序时造成的程序结构复杂。
线程的创建
调用pthread_create()函数,原形如下:
函数原形:
#include<pthread.h>
int pthread_create(pthread_t * thid ,pthread_attr_t ,(void*)(*start_routine)(void*),void*arg);
//函数名挺长,只需记住第一个参数为线程类型的指针,第三参数指向创建成功后要执行的函数,其中2 ,4参数分别默认为NULL
#include<stdio.h>
#include<pthread.h>
int * thread(void * arg){
pthread_t newthid ;
newthid = pthread_self() ;//获取新线程ID
printf("this is a new thread , thread ID = %u \n",newthid) ;
return NULL ;
}
int main(void){
pthread_t thid ;//定义一个线程类型的变量
//打印主线程ID
printf("main thread , ID is %u\n ",pthread_self());//
if(pthread_create(&thid , NULL , (void *)thread ,NULL) != 0){//
//创建线程,将所指的函数thread强转成void型,并执行函数
printf("thread create failed !\n");
exit(1);
}
sleep(1);
exit(0);
}
将多个线程调用的函数执行次数设为一次,调用pthread_once()函数:
#include<pthread.h>
int pthread_once(pthread_once_t *once_control ,void(*init_routine)(void));
//确保init_routine函数在进程中只执行一次。
应用示例:
#include<stdio.h>
#include<pthread.h>
pthread_once_t once = PTHREAD_ONCE_INIT ;
void run(void){
printf("Function run is running inthread %u\n",pthread_self());
}
void * thread1(void *arg){
pthread_t thid = pthread_self() ;
printf("Current thread's ID is %u \n",thid) ;
pthread_once(&once,run) ;//调用该函数确保函数run只执行一次
printf("thread1 ends\n ");
}
void *thread2(void *arg){
pthread_t thid = pthread_self();
printf("Current thread's ID is %u\n",thid);
pthread_once(&once,run);
printf("thread2 end!\n");
}
int main(void)
{
pthread_t thid1,thid2 ;
pthread_create(&thid1 , NULL , thread1 ,NULL);
pthread_create(&thid2 , NULL , thread2 ,NULL);
sleep(3);
printf("main thread exit .\n");
exit(0);
}
执行结果可以看出,run 函数尽管被两个线程调用,但执行次数是一次,感觉和ifndef和endif有点像吧。
线程终止:
线程终止最重要的就是释放资源,看到这儿,不知道心里是否会产生疑问,同一进程下的所有线程资源不是共享的吗?为什么会有线程终止这个说法,这正是要注意的点,线程终止主要是为了释放一些临界资源,因为临界资源在一段时间内只能被一个线程所持有。当线程要访问临界资源时,需提出请求,如果该资源未被使用,则申请成功,否则等待。所以当一个线程终止时,则其临界资源会被认为还被已经退出的线程使用,因而得不到释放,如果其他线程还等待使用该资源,就可能无限的等待下去,这就形成了死锁。所以还是有必要终止线程的。
使线程终止,第一种通过return从线程函数中返回;第二种调用pthread_exit();函数
函数原形:
#include<pthread.h>
pthread_exit(void * retval);
pthread_join(pthread_t th ,void* thread_return );//等待线程结束的函数
不介绍参数了,说那么多又记不住,还不如直接看用法,看栗子:
#include<stdio.h>
#include<pthread.h>
void assisthread(void * arg){
printf("I'm helping to go some jobs\n");
sleep(3);
pthread_exit(0);//退出线程
}
int main(){
pthread_t assisthid ;
int status ;
pthread_create(&assisthid , NULL , (void *)assisthread , NULL);
pthread_join(assisthid ,(void *)&status) ;//回收终止的线程资源
printf("assistthread's ecxit is caused %d\n" , status);
return 0 ;
}
私有数据
线程的私有数据可被函数访问,但对其他线程是私有的,线程私有数据采用一键多值技术,访问数据时,都是通过键值来访问的。使用线程私有数据时,首先为每个线程创建一个相关联的键在线程内部使用公用键的键来指代线程数据。
下面来看几个操作线程私有数据的函数,线程这块没办法,函数太多,而且还长,还是得多加练习:
原形:
#include<pthread.h>
int pthread_key_create(pthread_key_t *key ,void(destr_function)(void *) );
//创建一个键
int pthread_setspecific(pthread_key_t *key,const void* pointer);
//为一个键设置线程私有数据
int pthread_getspecific(pthread_key_t key);
//从一个键读取线程私有数据
int pthread_key_delete(pthread_key_t key);
//删除一个键
用法示例:
#include<stdio.h>
#include<pthread.h>
pthread_key_t key ;//为每个线程创建一个键
void * thread2(void * arg){//创建和使用线程的私有数据
int tsd ;
printf("thread %d is running \n" , pthread_self());
pthread_setspecific(key , (void *)tsd) ;//为该线程的key设置线程私有数据
printf("thread %d is returns %d \n",pthread_self(),pthread_getspecific(key)) ;
}
void thread1(void * arg){
int tsd = 0 ;
pthread_t thid2 ;
printf("thread %d is running \n",pthread_self());
pthread_setspecific(key , (void *)tsd);//创建一个线程私有数据
pthread_create(&thid2 , NULL , thread2 ,NULL);//创建线程2,创建成功后,让第三个函数指针指向thread1
sleep(5);
printf("thread %d returns %d \n",pthread_self(),pthread_getspecific(key));
}
int main(void){
pthread_t thid ;
printf("main thread begains running\n") ;
pthread_key_create(&key , NULL) ;//创建一个键
pthread_create(&thid1 ,NULL ,thread1 ,NULL) ;//创建一个线程
sleep(3);
pthread_key_delete(key) ;//删除一个键
return 0 ;
}
线程同步:
线程同步最大的特点就是资源的共享性,然而资源共享中的同步问题是多线程编程的难点。Linux常用互斥锁,条件变量,异步信号来解决线程间的同步问题。
要用到的函数原形:
#include<pthread.h>
#include<signal.h>
int pthread_mutex_init(pthread_mutex_t *mutex ,const mutexatrr *mutexattr );
//初始化互斥锁
int pthread_mutex_lock(pthread_mutex_lock * mutex );//设置互斥锁
int pthread_mutex_unlock(pthread_mutex_lock* mutex);//解除互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);//清除互斥锁
int pthread_cond_t_init(pthread_cond_t * cond ,pthread_condattr_t * cond_attr);
int pthread_cond_wait(pthread_cond_t* cond ,pthread_mutex_t * mutex);
//释放由mutex指向的互斥锁,同时使当前线程关于cond指向的条件变量阻塞,直到条件被信号唤醒。
int pthread_cond_signal(pthread_cond_t * cond);//激活等待条件成立的线程
int pthread_cond_destroy(pthread_cond_t *cond);
应用:
#include<stdio.h>
#include<pthread.h>
pthread_mutex_t mutex ;//设置互斥锁
pthread_cond_t cond ;//设置环境变量
void *thread1(void * arg)
{
pthread_cleanup_push(pthread_mutex_unlock , &mutex);
while(1){
printf("thread1 is running!\n");
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond ,&mutex);
printf("thread1 applied the condition!\n");
pthread_mutex_unlock(&mutex);
sleep(4);
}
pthread_cleanup_pop(0);
}
void * thread2(void * arg){
while(1){
printf("thread2 is running!\n");
pthread_mutex_lock(&mutex) ;
//设置互斥锁
pthread_cond_wait(&cond , &mutex) ;
//如果接受到条件成立的信号,则释放mutex指向的互斥锁,继续执行下面的任务
printf("thread2 apply the condition\n");
pthread_mutex_unlock(&mutex);//执行完任务就要释放解锁,上解锁函数应该同处于一个函数中
sleep(1);
}
}
int main(void){
pthread_t tid1 ,tid2 ;
printf("condition variabale study!\n");
pthread_mutex_init(&mutex , NULL);//初始化锁
pthread_cond_init(&cond , NULL);//初始化条件变量
pthread_create(&tid1 , NULL , (void *)thread1 ,NULL);
pthread_create(&tid2 , NULL , (void *)thread2 , NULL) ;
do{
pthread_cond_signal(&cond) ;
}while(1);//不断给线程1和线程2中wait函数交替发送信号,该信号按照入队顺序激活等待中的线程,实现线程同步执行任务
sleep(50);
pthread_exit(0);
}
线程这一章确实在设置互斥锁这儿要深究的细节问题比较多,所以以上只是将线程这一模块一些比较重要的函数及比较浅一点的用法简单的做了总结,肯定是不会就这么结束的。下一篇将深入总结线程中最常出现的问题。