一.線程的概念和使用
1.線程的概念
爲了進一步減少處理器的空轉時間,支持多處理器以及減少上下文切換開銷,進程在演化中出現了另外一個概念線程。
它是進程內獨立的一條運行路線,是內核調度的最小單元,也被稱爲輕量級的進程。
以前對線程和進程真的是傻傻分不清,面試中也經常會出現線程和進程的區別之類的問題。其實也比較好區分。
從上圖我們可以看出線程和進程的關係,圖片上看感覺線程是進程的子集?其實線程屬於輕量級的進程,假如我們的qq是一個進程,那麼裏面的聊天功能,打字功能等子模塊也需要一個個進程來完成嗎??顯然是可以完成的,但是感覺有點大材小用,切換時系統開銷肯定是很大的,用戶體驗也會非常的差,那麼就出現了線程。
同一進程中的線程共享相同的地址空間。當然線程有共有的部分,也有自己私有的部分(TCB 堆棧 寄存器等)。
2.線程的特點:
1> 大大提高了任務的切換效率
2> 避免了額外的TLB & cache的刷新。那什麼是cache,它其實就是一個高速內存,它很快到但是它也很小,cpu存取要先從cache中找,當沒有的時候cpu會通過TLB(內存映射),將內存中的數據放入cache中,如果進程較大,就需要cache頻繁切換,效率下降。
3.進程資源佔用
一個進程中可以有多個線程,線程之間有一些資源是共享的 ,就像進程一樣,有私有的也有自己獨有的部分。
一般共享以下資源:
可執行的指令,靜態數據,進程中打開的文件描述符,當前工作目錄,用戶ID,用戶組ID
私有資源部分:
線程ID (TID),PC(程序計數器)和相關寄存器,堆棧,錯誤號 (errno),優先級,執行狀態和屬性
二.線程的基本操作
需要使用線程庫,pthread線程庫中提供瞭如下基本操作
1.創建線程
#include <pthread.h>
int pthread_create(pthread_t *thread, const
pthread_attr_t *attr, void *(*routine)(void *), void *arg);
例:
void *funct(void* arg){
printf("this is thread ");
sleep(5);
}
int main(){
int re,i;
pthread_t tid;
for(i=0;i<100;i++){
re = pthread_create(&tid, NULL,funct, NULL);
pthread_detch(tid); //後面會講,用於回收
if(re!=0){
printf("pthread_create:%s\n",strerror(re));
exit(0);
}
}
sleep(1); //需要加延時,不然進程就結束了,線程得不到運行也跟着退出了
}
注:進程結束,線程也會跟着結束。
ps -eLf|grep cthread //來查看進程中的線程
多線程程序中,任何一個線程使用exit(0)都會導致整個進程結束。
2.回收線程
線程不回收就會出現殭屍線程,同樣也要回收。
先查看線程ID
然後top -p 2933(進程號) 查看內存佔用。
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
if (pthread_create(&tid, NULL, thread_func, NULL) != 0){
printf(“fail to pthread_create”); exit(-1);
}
re = pthread_join(tid,NULL);
if(re!=0){
printf("pthread_join:%s\n",strerror(re));
return;
}
}
這樣就完成指定線程的釋放,此時的第二個參數下面會用到。
3.結束線程
void pthread_exit(void *retval); 返回值,可被pthread_join回收,通常配合join使用
void *funct(void* arg){
int ret;
printf("this is funct thread\n");
sleep(1);
ret = 5;
pthread_exit((void*)ret); //線程退出,將參數返回
}
int main(){
int re,i;
pthread_t tid;
re = pthread_create(&tid, NULL,funct, (void *)i);
void *retval;
pthread_join(tid,&retval);// 獲取exit的返回參數
printf("retval=%d\n",(int)retval);
}
4.線程取消
int pthread_cancel(pthread_t thread);
本質上給線程發一個信號,這個使用前要確保線程是能被取消屬性,並且是不是在cancel點(阻塞函數 sleep等)上此時才能取消成功.
還有一些配合使用的函數:
1》 int pthread_setcancelstate(int state, int *oldstate); 設置線程屬性是可以被取消還是不可以,如果設置了不可取消屬性,調用pthread_cancel,也不能被取消。
例:pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);//設置爲不可取消
2》int pthread_setcanceltype(int type, int *oldtype);設置時延時取消還是立即取消
如果爲延時取消,就運行到cancel點時取消,阻塞函數,sleep等都是cancel點.
如果沒有cancel點呢??可以人爲的加一個void pthread_testcancel(void);通過這個函數。立即取消不需要cancel點。
5.修改detach屬性
pthread_detach,改變爲detach屬性後,不需要再用join進行回收,感覺這個更方便。
re = pthread_create(&tid, NULL,funct, (void *)i);
pthread_detach(tid);
三.線程間同步互斥機制
我們在同一個進程中創建了多個線程,他們共享地址空間,通過全局變量就可以進行數據的交互,當時多個線程訪問共享資源的時候就需要同步和互斥,和實時系統中是一樣的。以前我們在實時系統中都知道用信號量進行操作,互斥信號量進行互斥,這裏都是一樣的。
在linux中它有更洋氣的名字叫原子操作。
操作方式:
1.初始化
2.P操作(申請資源)
3.V操作(釋放資源)
原理上沒啥區別。
posix中定義了兩類信號量:
無名信號量(基於內存的信號量)只用於線程間同步和互斥
有名信號量,保存在文件中,可以用於線程也可以用於進程的同步和互斥
1.線程的同步
int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_wait(sem_t *sem); // P操作
int sem_post(sem_t *sem); // V操作
例:
#define NUM 64
sem_t sem;
char buf[NUM];
void * writeBuff(void * arg){
while(1){
fgets(buf,NUM,stdin);
sem_post(&sem); //P操作
}
}
void *readBuff(void * arg){
while(1){
sem_wait(&sem); //V操作
printf("buf=%s\n",buf);
memset(buf,0,NUM);
}
}
int main(){
pthread_t tid1,tid2;
int re;
sem_init(&sem,0,0);//初始化信號量
re = pthread_create(&tid1, NULL,writeBuff, NULL);
if(re!=0){
printf("pthread_create:%s\n",strerror(re));
exit(0);
}
re = pthread_create(&tid2, NULL,readBuff, NULL);
if(re!=0){
printf("pthread_create:%s\n",strerror(re));
exit(0);
}
while(1){
sleep(1);
}
}
2.線程的互斥
mutex互斥鎖,和我們平時用的也沒啥區別,也是一種信號量
任務訪問臨界資源前申請鎖,訪問完後釋放鎖
int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t * attr);
int pthread_mutex_lock(pthread_mutex_t *mutex);申請鎖
int pthread_mutex_unlock(pthread_mutex_t *mutex);釋放鎖
FILE *fp;
pthread_mutex_t mutex;
void *write1(void* arg){
int a=0;
a = (int)arg;
int len,i;
char *c1 = "Hello world\n";
char *c2;
len = strlen(c1);
int td = pthread_self();
pthread_detach(pthread_self());
c2 = c1;
while(1){
pthread_mutex_lock(&mutex);//上鎖
for(i=0;i<len;i++){
fputc(*c1,fp);
fflush(fp);
c1++;
usleep(10000);
}
pthread_mutex_unlock(&mutex);//解鎖
c1 = c2;
sleep(1);
}
}
void *write2(void* arg){
int a=0;
a = (int)arg;
int len,i;
char *c1 = "How are your\n";
char *c2;
c2 = c1;
len = strlen(c1);
int td = pthread_self();
pthread_detach(pthread_self());
while(1){
pthread_mutex_lock(&mutex);//上鎖
for(i=0;i<len;i++){
fputc(*c1,fp);
fflush(fp);
c1++;
usleep(10000);
}
pthread_mutex_unlock(&mutex);//解鎖
c1 = c2;
sleep(1);
}
}
int main(){
int re,i=0;
pthread_t tid1,tid2;
fp = fopen("1.txt","w");
if(!fp){
perror("fopen");
return -1;
}
pthread_mutex_init(&mutex,NULL);
re = pthread_create(&tid1, NULL,write1, (void *)i);
pthread_detach(tid1);
if(re!=0){
printf("pthread_create:%s\n",strerror(re));
exit(0);
}
re = pthread_create(&tid2, NULL,write2, (void *)i);
pthread_detach(tid2);
if(re!=0){
printf("pthread_create:%s\n",strerror(re));
exit(0);
}
while(1){
sleep(1);
}
}
注:我們在使用線程函數的時候要注意,我們要使用線程庫,在編譯的時候也要注意要鏈接上庫,不然編譯會報錯
gcc -o pthread pthread.c -lpthread 詳細的可以查看庫的生成那一節