1. 线程概念:线程是包含在进程内部的顺序执行流,是进程中实际运作单位,也是操作系统能够进行调度的最小单位,一个进程中可以并发多条线程,每条线程并行执行不同的任务。
2. 线程与进程的关系
1> 一个线程只能属于一个进程,但是一个进程可以有多个线程,但至少有一个主线程。
2> 同一进程的所有线程共享该进程的所有资源。
3> 线程作为调度和分配的基本单位,进程作为拥有资源的基本单位。
4> 进程是拥有资源的一个独立单位,线程不拥有系统资源,但是可以访问隶属于进程的资源
3. 多线程的优势
1> 方便的通信和数据交换,同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其他线程所用
2> 更高效的利用CPU,提高应用程序的响应。
demo1: 主线程创建5个线程,这5个线程和主线程并发执行,主线程创建完线程后调用pthread_exit()函数退出线程,在每个线程内分别打印当前线程的序号。
#include<pthread.h> #include<stdio.h> #include<stdlib.h> #define Threads_number 5 void *PrintHello(void* threadid) { long tid; tid = (long)threadid;//强制类型转换 printf("Hello World!It's me, thread #%ld!\n", tid); pthread_exit(NULL);//退出线程 } int main(int argc, char* argv[]) { pthread_t threads[Threads_number]; int rc; long t; for(t = 0; t < Threads_number; t++) { printf("In main: creating thread %ld\n", t); //创建线程并传入参数 rc = pthread_create(&threads[t], NULL, PrintHello,(void*) t); if(rc) { printf("ERROR"); exit(-1); } } printf("In main: exit!\n"); pthread_exit(NULL); return 0; }运行效果图:
线程可以分为分离线程和非分离线程两种:
分离线程:指线程退出时线程将释放它的的资源的线程。
非分离线程:线程退出后不会立即释放资源,需要另一个线程为它调用pthread_join函数或者进程退出时才会释放资源。
总结:线程可以自己来设置分离也可以由其他线程来设置分离,函数int pthread_detach(pthread_t thread) 可以将非分离线程设置为分离线程。该函数成功返回0,失败返回一个非0的错误码.
demo2: 主线程创建了4个线程来进行数学运算,每个线程将运算结果使用pthread_exit()函数返回给主线程,主线程使用pthread_join()等待4个线程完成和获取线程的运行结果。
#include<pthread.h> #include<stdio.h> #include<stdlib.h> #include<math.h> #define Threads_number 4 void *BusyWork(void* t)//线程函数 { int i; long tid; double result = 0.0; tid = (long)t; printf("Thread %ld starting...\n", tid); for(i = 0; i < 1000000; i++) { result = result + sin(i)*tan(i); } printf("Thread %ld done,Result = %e\n", tid, result); pthread_exit((void*)t);//带计算结果退出 } int main(int argc, char* argv[]) { pthread_t threads[Threads_number]; int rc; long t; void* status; for(t = 0; t < Threads_number; t++) { printf("In main: creating thread %ld\n", t); //创建线程并传入参数 rc = pthread_create(&threads[t], NULL, BusyWork,(void*) t); if(rc) { printf("ERROR"); exit(-1); } } for(t = 0; t < Threads_number; t++) { //以阻塞的方式等待thread[t]指定的线程结束,status为线程返回值 rc = pthread_join(threads[t], &status); if(rc) { printf("error"); exit(-1); } printf("Main: completed join with thread %ld having a status of %ld\n", t, (long)status); } printf("In main: exit!\n"); pthread_exit(NULL); return 0; }
线程的基本属性包括:栈大小、调度策略和线程状态
demo3: 下面的例程主要说明线程的创建以及线程属性的使用方法,主线程根据参数列表的参数给出线程栈大小来设置线程属性对象,然后为参数列表的剩余参数分别创建线程来实现小写转大写的功能及打印栈地址。
#include <pthread.h> #include <string.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <ctype.h> //出错处理宏供返回错误码的函数使用 #define handle_error_en(en, msg) \ do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0) //出错处理宏 #define handle_error(msg) \ do { perror(msg); exit(EXIT_FAILURE); } while (0) struct thread_info { pthread_t thread_id; int thread_num; char *argv_string; }; //线程运行函数 static void *thread_start(void *arg) { struct thread_info *tinfo = arg; char *uargv, *p; /*通过p的地址来计算栈的起始地址*/ printf("Thread %d: top of stack near %p; argv_string=%s\n", tinfo->thread_num, &p, tinfo->argv_string); uargv = strdup(tinfo->argv_string); if (uargv == NULL) handle_error("strdup"); for (p = uargv; *p != '\0'; p++) *p = toupper(*p);//将小写字符转换为大写字符 return uargv;//返回字符串的起始地址 } int main(int argc, char *argv[]) { int s, tnum, opt, num_threads; struct thread_info *tinfo; pthread_attr_t attr; int stack_size; void *res; stack_size = -1; while ((opt = getopt(argc, argv, "s:")) != -1) { /*处理参数-s所指定的栈大小*/ switch (opt) { case 's': //“:”表示必须该选项带有额外的参数,全域变量optarg会指向此额外参数 stack_size = strtoul(optarg, NULL, 0); break; default: fprintf(stderr, "Usage: %s [-s stack-size] arg...\n",argv[0]); exit(EXIT_FAILURE); } } //全域变量optind指示下一个要读取的参数在argv中的位置。 num_threads = argc - optind; s = pthread_attr_init(&attr);//初始化属性对象 if (s != 0) handle_error_en(s, "pthread_attr_init"); if (stack_size > 0) { //设置属性对象的栈大小 s = pthread_attr_setstacksize(&attr, stack_size); if (s != 0) handle_error_en(s, "pthread_attr_setstacksize"); } //在内存中动态地分配 num 个长度为 size 的连续空间,并将每一个字节都初始化为 0 tinfo = calloc(num_threads, sizeof(struct thread_info)); if (tinfo == NULL) handle_error("calloc"); for (tnum = 0; tnum < num_threads; tnum++) { tinfo[tnum].thread_num = tnum + 1; tinfo[tnum].argv_string = argv[optind + tnum]; //根据属性创建线程 s = pthread_create(&tinfo[tnum].thread_id, &attr,&thread_start, &tinfo[tnum]); if (s != 0) handle_error_en(s, "pthread_create"); } s = pthread_attr_destroy(&attr);//销毁属性对象 if (s != 0) handle_error_en(s, "pthread_attr_destroy"); for (tnum = 0; tnum < num_threads; tnum++) { //等待线程终止,并获取线程返回值 s = pthread_join(tinfo[tnum].thread_id, &res); if (s != 0) handle_error_en(s, "pthread_join"); printf("Joined with thread %d; returned value was %s\n",tinfo[tnum].thread_num, (char *) res); free(res); } free(tinfo); exit(EXIT_SUCCESS); }
demo4: 使用互斥量来保证多线程同时输出,然后通过互斥量保证获取的线程打印完才让别的线程打印
#include<stdio.h> #include<string.h> #include<pthread.h> #include<stdlib.h> #include<unistd.h> pthread_t tid[2]; pthread_mutex_t lock; //创建一个锁变量 //线程1和线程2同时都要调用同一临界资源 此处通过锁互斥 void* doPrint(void *arg) { int id = (long)arg; int i = 0; pthread_mutex_lock(&lock);//加锁 printf("Job %d started\n", id); for (i = 0; i < 5; i++) { printf("Job %d printing\n", id); usleep(10); } printf("Job %d finished\n", id); pthread_mutex_unlock(&lock);//释放锁 return NULL; } int main(void) { long i = 0; int err; //动态初始化锁变量 if (pthread_mutex_init(&lock, NULL) != 0) { printf("\n Mutex init failed\n"); return 1; } while(i < 2) { err = pthread_create(&(tid[i]), NULL, &doPrint, (void*)i); if (err != 0) printf("Can't create thread :[%s]", strerror(err)); i++; } pthread_join(tid[0], NULL); pthread_join(tid[1], NULL); pthread_mutex_destroy(&lock);//销毁锁 return 0; }
上面代码中如果修改不使用互斥量,则可以看到输出为乱序。
死锁与避免
死锁:指两个或两个以上的执行序在执行过程中因为争夺资源而造成的一种互相等待的现象。例如:一个线程T1已锁定了一个资源R1,又想去锁定资源R2,而此时另一个线程T2已锁定了资源R2,却去想锁定资源R1,两个线程都想得到对方的资源而不愿释放自己的资源,造成两个线程都在等待,而无法执行的情况。
demo5: 示例死锁发生的情况,程序创建了两个线程,第一个线程先获取mutexA锁,再获取mutexB锁,第二个线程先获取mutexB后获取mutexA锁,这时死锁就可能发生。
#include<stdio.h> #include<string.h> #include<pthread.h> #include<stdlib.h> #include<unistd.h> pthread_t tid[2]; //静态初始化互斥量 pthread_mutex_t mutexA = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t mutexB = PTHREAD_MUTEX_INITIALIZER; void * t1(void *arg) { pthread_mutex_lock(&mutexA);//线程1获取mutexA锁 printf("t1 get mutexA\n"); usleep(1000); pthread_mutex_lock(&mutexB);//线程1获取mutexB锁 printf("t1 get mutexB\n"); pthread_mutex_unlock(&mutexB);//线程1释放mutexA锁 printf("t1 release mutexB\n"); pthread_mutex_unlock(&mutexA);//线程1释放mutexB锁 printf("t1 release mutexA\n"); return NULL; } void * t2(void *arg) { pthread_mutex_lock(&mutexB);//线程2获取mutexB锁 printf("t2 get mutexB\n"); usleep(1000); pthread_mutex_lock(&mutexA);//线程2获取mutexA锁 printf("t2 get mutexA\n"); pthread_mutex_unlock(&mutexA); printf("t2 release mutexA\n"); pthread_mutex_unlock(&mutexB); printf("t2 release mutexB\n"); return NULL; } int main(void) { int err; long i; //创建一个线程 调用线程1函数 err = pthread_create(&(tid[0]), NULL, &t1, NULL ); if (err != 0) printf("Can't create thread :[%s]", strerror(err)); err = pthread_create(&(tid[1]), NULL, &t2, NULL); if (err != 0) printf("Can't create thread :[%s]", strerror(err)); for (i = 0; i < 2; i++) pthread_join(tid[i], NULL); return 0; }
程序会一直卡在这里!发生了死锁。
死锁的避免:当多个线程需要相同的一些锁,但是按照不同的顺序加锁,死锁就很容易发生,如果能确保所有的线程都是按照相同的顺序获得锁,那么死锁就不会发生。比如规定程序内有三个互斥锁的加锁顺序为mutexA->mutexB->mutexC,则线程t1,t2,t3线程操作伪代码如下就不会出现死锁:
t1 t2 t3
lock(mutexA) lock(mutexA) lock(mutexB)
lock(mutexB) lock(mutexC) lock(mutexC)
lock(mutexC)