1.线程存在于进程中,当运行一个程序的时候,linux创建一个新进程,这个新进程中创建了一个主线程,该主线程又能创建其他的线程
2.创建一个新进程的时候,子进程在父进程中初始化运行,父进程的虚拟内存和文件描述符等复制给了子进程,子进程能够修改内存,关闭文件描述符并且不影响父进程,同样的,父进程中的这些操作也不会影响到子进程.
3,当创建一个新线程的时候,不存在任何的拷贝现象,正在创建和已经创建的线程共享内存空间,文件描述符等系统资源,当其中一个线程修改了内存或者关闭了文件描述符,立马就能影响到其他的线程运行.由于一个进程和该进程中的所有线程同一时间只能执行同一个程序,因此只要某个线程执行了exec函数,所有的线程就都停止了.
4.linux中和线程相关的在头文件<pthread.h>中,在链接时要加上libpthread.so,也即加上-lpthread选项.进程中的线程用线程id来区分(pthread_t类型的变量),每个线程一创建就会开始执行线程函数,该函数退出了线程也随之退出,
5.用pthread_create创建了一个新线程后,原线程继续执行,同时新建线程也开始执行线程函数.pthread_create函数有四个参数,第一个是pthread_t变量指针,指针指向新创建的线程id,一个线程属性对象的指针,指向线程函数的函数指针,以及线程函数中的形参指针(void *类型)
#include<stdio.h> #include<pthread.h> #include<stdlib.h> #include<unistd.h> void *printx(void *unused) { int i = 0; while(i < 10) { fprintf(stderr,"x\n"); i++; } return NULL;//或者pthread_exit(NULL);也可以退出该线程 } int main() { pthread_t thread_id; pthread_create(&thread_id,NULL,printx,NULL); while(1) { fprintf(stderr,"o\n"); sleep(2); } return 0; }
此时存在一种情况就是如果主线程以及结束了但分线程还未结束,程序就会等不到分线程的执行结果就会退出.分线程的退出有两种情况,一种的在线程函数中return,另一个就是在线程函数中显示的调用pthread_exit()函数,pthread_exit函数的参数为线程函数要return的类型.
6.pthread_create的第四个参数是传递给线程函数的形参,一般用一个struct变量将要传递的参数值储存起来,下面给出创建两个线程,每个线程执行相同的函数,但传递给线程函数的参数不同#include<stdio.h#include<pthread.h#include<unistd.h>
struct params { char character; int count; }; void *print_params(void *parameters) { struct params * p = (struct params *)parameters; for(int i = 0;i < p->count;i++) { printf("%c\n",p->character); fflush(stdout); } return NULL; } int main() { pthread_t thread_id1,thread_id2; struct params p1,p2; p1.character = 'x'; p1.count = 10; p2.character = 'o'; p2.count = 5; pthread_create(&thread_id1,NULL,print_params,&p1); pthread_create(&thread_id2,NULL,print_params,&p2); pthread_join(thread_id1,NULL);
pthread_join(thread_id2,NULL);
}
此时存在一个问题就是作为主线程内的局部变量p1,p2,两个分线程里有指针指向这两个变量,主线程结束后p1,p2释放了,但分线程仍然去获取这两个变量的值,因此就会发生错误,解决办法就是用pthread_join函数去等待分线程执行的结束,类似于进程中的wait函数功能,pthread_join函数的第一个参数是要等待的线程id,第二个参数是指向该线程的返回值的指针(void *),若不care线程返回值,可以用NULL代替.若想得到线程的返回值,可以通过如下方式获得:
#include<stdio.h> #include<pthread.h> #include<unistd.h> #include<stdlib.h> struct params { char character; int count; }; void* print_params(void *parameters) { struct params * p = (struct params *)parameters; for(int i = 0;i < p->count;i++) { printf("%c\n",p->character); fflush(stdout); } int temp = 3; return (void *)temp; } int main() { pthread_t thread_id1,thread_id2; struct params p1,p2; p1.character = 'x'; p1.count = 10; p2.character = 'o'; p2.count = 5; pthread_create(&thread_id1,NULL,print_params,&p1); pthread_create(&thread_id2,NULL,print_params,&p2); int res; pthread_join(thread_id1,(void **) &res); printf("res=%d\n",res); pthread_join(thread_id2,NULL); return 0; }
输出为:
x x x x x x x x x x o o o o o res=3
7.为了防止在自己线程内pthread_join自己这个线程的id,在join之前可以通过pthread_equal判断是否在自己线程内:
if(!pthread_equal(pthread_self(),otherThreadId)) pthread_join(otherThreadId,NULL);
8.前面说到pthread_create函数的第二个参数是要创建的线程属性,如果用默认值的话该位置的值为NULL,若要用到特定的一些线程属性,需要遵循以下步骤来生成带特定线程属性的线程:
创建一个pthread_attr_t对象,调用pthread_attr_init()函数对该对象进行初始化并将其置为默认值,函数中参数为一个指针.对attr对象进行修改,然后往pthread_create中传递attr指针.调用pthread_attr_destory函数对attr对象进行销毁.
pthread_t thread_id; pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);//此时创建的thread不需要join,在运行结束后就自动释放资源 pthread_create(&thread_id,&attr,printx,NULL); pthread_attr_destroy(&attr);
9.在一个线程中可以取消另一个线程,用函数pthread_cancel,参数是thread id,针对取消线程这个步骤,可以将线程分为三种情况,异步线程可以在线程执行的任意时候被取消,同步线程也可以被取消,但只能在执行到某个特定的点才可以执行取消线程步骤,而且会对接收到的取消线程请求进行排队.还有一种是无法被取消的线程,该线程会忽视传来的线程取消请求.默认线程在创建的时候是同步线程.可以通过函数pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL)函数来改变线程的取消属性(同步与异步),用pthread_setcancelstate (PTHREAD_CANCEL_DISABLE, NULL);函数来设定线程是否可以被取消.
10.在同步线程中,只有遇到取消点并且受到了取消信号后才会取消该线程,用pthread_testcancel函数可以设置一个取消点.
11.在同一个程序下的所有线程共享地址空间,也就意味着如果某个线程改变了内存中某处的值,其他线程也能够看到这个改变.但每个单独的线程又有自己的栈,有时候又需要在每个线程上为某个变量建立拷贝,这些变量可以称之为线程特指变量,存储在这块区域中的变量为每个线程建立一个拷贝,线程在自己的执行过程中修改这个拷贝并不影响其他线程中的值,pthread_key_create函数可以用来建立该变量,函数的第一个参数是指向pthread_key_t变量的指针,第二个参数是一个函数指针,当将线程专属值传递给线程的时候会自动执行函数指针所指向的函数,称之为清理函数.在线程中可以通过pthread_setspecific来为专属变量进行赋值,通过pthread_getspectic来获取专属变量的值.
#include<stdio.h> #include<pthread.h> #include<stdlib.h> pthread_key_t thread_long_key; void write_to_log(const char * message) { FILE* thread_log = (FILE *)pthread_getspecific(thread_long_key); fprintf(thread_log,"%s\n",message); } void close_thread_log(void *thread_log) { fclose((FILE *)thread_log); } void *thread_func(void *arg) { char thread_log_name[256]; sprintf(thread_log_name,"threads_%d.log",(int)pthread_self()); FILE *thread_log = fopen(thread_log_name,"w"); pthread_setspecific(thread_long_key,thread_log); write_to_log("process start\n"); return NULL; } int main() { pthread_t pthread[5]; pthread_key_create(&thread_long_key,close_thread_log);//close_thread_log函数被指定为生成的key的清理函数,因此在thread_fun中无需close文件描述符. for(int i = 0;i < 5;i++) { pthread_create(pthread + i,NULL,thread_func,NULL); } for(int i = 0;i < 5;i++) { pthread_join(*(pthread + i),NULL); } return 0; }
12.也可以通过设置一个cleanup handlers函数,使得线程退出时自动调用该函数,
pthread_cleanup_push函数可以注册一个cleanup handlers函数,第一个参数是要注册的函数指针,第二个参数是要传递给handler函数的参数指针,pthread_cleanup_pop函数用来对cleanup handlers函数进行解注册,若传递一个非零值给pthread_cleanup_pop函数,线程在自动执行cleanup handlers函数后解注册.
void dellocate(void *buffer) { free(buffer); } int main() { void *temp_buffer = malloc(1024); pthread_cleanup_push(&dellocate,temp_buffer);//第一个参数是函数指针,第二个参数是传递向该函数的参数指针 //do someshing pthread_cleanup_pop(1);//在解注册函数dellocate之前会先执行dellocate函数. }
13.为了防止线程竞争同一个资源,比如同时读写某个变量导致错误,引入了互斥锁的概念(pthread_mutex_t),
pthread_mutex_t mutex; pthread_mutex_init(&mutex,NULL);//第二个参数是锁的属性,NULL代表取默认属性
以上两句等同于
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&mutex);//对mutex加上锁,当线程执行到这里的时候,若锁已经被加上了,线程停止在这里直至锁被其他线程解开
//do something
pthread_mutex_unlock(&mutex);//对mutex解锁,解开锁后随即选择某一阻塞在加锁出的线程.
14.互斥锁死锁,假设以下情况;
pthread_mutex_t mutex; pthread_mutex_init(&mutex,NULL);//第二个变量是指向互斥锁属性的指针 pthread_mutex_lock(&mutex); //do something pthread_mutex_lock(&mutex);//即连续对同一个互斥锁进行加锁操作,此时会出现死锁现象,即在等待永远不可能发生的事情发生
,锁有三种类型,快速互斥锁(默认的),递归锁和错误检测互斥锁,递归锁在出现这种情况的时候会记录下当前线程内对该互斥锁进行了几次的加锁,在解锁的时候也得解锁同样次数才算完全解锁,错误检测互斥锁在这种情况下会进行标记并返回错误.默认创建的是快速互斥锁,要想创建递归互斥锁或者错误检测互斥锁的话,要通过pthread_mutexattr_setkind_np函数:
pthread_mutex_t mutex; pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_setkind_np(&attr,PTHREAD_MUTEX_RECURSIVE_NP);//递归互斥锁,PTHREAD_MUTEXATTR_ERRORCHECK_NP pthread_mutex_init(&mutex,&attr);
有时候我们仅仅需要测试一下互斥锁是否被锁住,若锁住了就转而执行其他操作,可以通过函数pthread_mutex_trylock函数,若当前互斥锁未被锁住,和函数pthread_mutex_lock的效果一样,若该互斥锁已经被其他线程锁住了,会立马返回一个错误代码EBUSY,并对其他线程的锁操作无影响.
15.有时候我们会用多个线程来处理一个队列,每个线程去队列里取任务,若线程处理速度太快,队列得到任务速度跟不上,线程就会退出,以后任务再来就会出现无线程进行操作的现象发.针对这种情形,我们用信号量(semaphore)来避免这种事情的发生,每个信号量是一个非负数值,可以用来对多线程进行同步,一个信号量支持两种基本操作,wait和post操作,wait操作将信号量的值减去1,如果值已经是0,该操作被阻塞直至信号量的值变为正(其他线程的操作),当信号量的值变为正后,wait函数将信号量值减去1并返回,post操作将信号量的值加上1,如果信号量之前的值是0并且有线程被阻塞在信号量的wait操作上,其中一个线程解除阻塞并且该该线程wait函数返回(也即信号量重新变为0).linux提供了两种信号量的实现,一种是用于线程间通信,另一种是用于进程间的通信,现在说的是第一种,要包含头文件semaphore.h.
16.sem_t代表信号量变量,要用sem_init进行初始化,sem_init函数第一个参数是指向sem_t变量的指针,第二个参数应该是0,第三个变量是信号量的初始值.当不需要这个信号量变量了,要用sem_destroy进行销毁.sem_wait和sem_post代表两种操作,同样也有一个非阻塞的函数sem_trywait.sem_getvalue可以用来获取信号量的值.
回到上面的情况,任务队列初始时长度为0,初始化信号量为0,当任务入队列的时候进行sem_post(&sem_count)操作,当获取任务进行执行之前,执行sem_wait(&sem_count)操作,这样当队列中没任务的时候,线程会阻塞在sem_wait函数处,直至sem_post操作被执行(也即新来一个任务入队列).
17.条件变量同样可以用来保护多线程环境中的变量,假设在一个无限循环的代码中,需要检测某个变量是否被设置,若被设置了就执行下一步的代码,若没被设置,继续执行循环,这个可以通过如下效率不高的方式实现:
int thread_flag = 0; pthread_cond_t cond; pthread_mutex_t mutex; pthread_mutex_init(&mutex,NULL); pthread_cond_init(&cond,NULL); void *thread_func(void *args) { int flag; while(1) { pthread_mutex_lock(&mutex); flag = thread_flag; pthread_mutex_unlock(&mutex); if(flag)//若flag没有被执行,继续循环. do_work(); } return NULL; } int set_flag_func(ing value) { pthread_mutex_lock(&mutex); thread_flag = value; pthread_mutex_unlock(&mutex); }
在此情况下可以通过条件变量使得程序更加高效,具体做法是在while循环出等待条件变量被设置,set_flag_func中设置完thread_flag后通知while循环变量已经设置好了,可以进行下一步的操作了.可以是如下代码:
int thread_flag = 0; pthread_mutex_t mutex; pthread_cond_t cond; pthread_mutex_init(&mutex,NULL); pthread_cond_init(&cond,NULL); void *thread_func(void *args) { int flag; while(1) { while(!thread_flag) { pthread_mutex_lock(&mutex); pthread_cond_wait(&cond,&mutex);//第二个参数是已经被锁住的互斥锁,当线程在此处被阻塞时,首先解开互斥锁,然后等待被唤醒,pthread_cond_signal函数唤醒一个被pthread_cond_wait阻塞的线程,pthread_cond_broadcast唤醒所有被pthread_cond_wait阻塞的线程.当收到信号被唤醒后又会首先锁住互斥锁,然后继续执行pthread_cond_wait后面的代码, pthread_mutex_unlock(&mutex); do_work(); } } return NULL; } int set_flag_func(ing value) { pthread_mutex_lock(&mutex); thread_flag = value; pthread_cond_signal(&cond);//唤醒一个被pthread_cond_wait阻塞的线程. pthread_mutex_unlock(&mutex); }