线程学习第一课--线程同步
在一个程序中的多个执行路线叫做线程
线程是一个进程内部的一个控制序列
当进程执行fork调用时,创建出该进程的一份新的拷贝,这个新的进程拥有自己的变量和pid
时间调度是独立的,执行也几乎是独立的。
当进程创建一个新的线程的时候,新的执行线程将有自己的栈,但是和创建者共享局部变量,
文件描述符,信号句柄和当前目录状态。
线程的优点:
有时候让程序同时做两件事情是很有用的;对于数据服务器来说,这个明显的多任务工作如果用多进程
的方式来完成将很难做到高效率,因为各个不同的进程必须紧密合作才能满足加锁和数据一致性方面的
要求,而用多线程来完成就比多进程要容易的多。
一个混杂这输入,计算和输出的程序,可以将这几个部分分离为三个线程来执行,从而改善程序的执行
的性能。
一般的线程之间的切换需要操作系统做的工作比进程之间切换少得多,因此多个线程对资源的需求要远
小于多个进程
线程的缺点是:
线程的设计可以非常的复杂,是一个立刻让自己自讨苦吃的方式。
多线程的调试比单线程的调试要困难的多,因为线程之间的交互非常难以控制。
将大量的计算分成两个部分,并把两个部分作为两个不同的线程来运行在一台单处理器上不一定获得更快
的运行效率,因为中的cpu速度是一定的。
第一个简单的线程程序:创建新的线程,并等待新的线程结束
首先因为一个全局变量可能被多个线程共享造成全局变量的不够用或者不精确,为了解决这个问题,我们需要
可重入的例程,可重入的代码可以被多次调用而仍然正常工作。代码的可重入部分通常使用局部变量,这使得每次
对该代码的调用都将获得它自己的唯一的一份数据拷贝。
宏_REENTRANT来告诉编译器我们需要可重入功能。
它主要完成下面的三件事情:
对部分函数重新定义它们的可安全重入的版本,这些函数的名字一般不会改变只是函数名后面添加-r字符串。
stdio.h中原来以宏的形式实现的一些函数将变成可安全重入的函数。
在errno.h中定义的变量errno现在将变成一个函数调用,它能够以一种安全的多线程方式来获取真正的errno值。
pthread_create函数的作用是创建一个新线程,类似于创建新进程的fork函数
1 #include <pthread.h> 2 int pthread_create(pthread_t * thread, pthread_attr_t * attr, void * (*start_routine)(void *), void *arg); 3 ____________________ ___________ _______________________________________ _______
第一个参数是指向pthread_t类型的指针,线程被创建时候,这个指针指向的变量中被写入一个
标识符,我们可以用这个标识符来引用新的线程。
第二个参数是设置线程的属性,一般不要特殊的属性的时候就设置为NULL
第三个参数是线程将要启动执行的函数,我们需要传入一个函数地址,该函数以一个指向void
的指针为参数,返回值也是一个指向void的指针,因此传递任一类型的参数并返回一个任一类型
的指针。fork调用后父子进程将在同一位置继续执行下去,但是新创建的线程必须明确提供给
它一个函数指针,新线程将在这个新位置开始执行。
第四个参数是传递给这个函数的参数
函数成功时返回0失败时返回错误代码
pthread_exit函数可以使线程终止执行
1 #include <pthread.h> 2 void pthread_exit(void * retval);
函数终止调用它的线程并返回一个指向某个对象的指针
pthread_join函数在线程中等价于用在进程中收集子进程信息的wait函数
1 #include <pthread.h> 2 int pthread_join(pthread_t th, void ** thread_return);
第一个参数指定了要等待的线程,线程通过pthread_create返回的标识符来指定
第二个参数是一个指针,它指向了另外一个指针,而否则指向{线程的返回值}
成功的时候返回0失败的时候返回错误代码
简单的线程程序例子:
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <pthread.h> 5 6 void *thread_function(void *arg);//定义了函数原形 7 8 char message[] = "Hello World";//定义了全局变量字符串数组 9 10 int main() 11 { 12 int res; 13 pthread_t a_thread; 14 void *thread_result; 15 res = pthread_create(&a_thread, NULL, thread_function, (void *)message); 16 //创建新的线程,传递一个pthread_t结构的地址,设置默认,执行函数,和函数参数是最后两个 17 if (res != 0) 18 { 19 perror("Thread creation failed"); 20 exit(EXIT_FAILURE); 21 } 22 printf("Waiting for thread to finish...\n");//创建成功,打印一句话,现在有两个线程 23 res = pthread_join(a_thread, &thread_result);//等待新创建的线程的结束,得到指向线程返回值的指针 24 if (res != 0) { 25 perror("Thread join failed"); 26 exit(EXIT_FAILURE); 27 } 28 printf("Thread joined, it returned %s\n", (char *)thread_result);//新线程执行完毕,打印返回值 29 printf("Message is now %s\n", message);//重新打印新的全局变量 30 exit(EXIT_SUCCESS); 31 } 32 33 void *thread_function(void *arg) 34 { 35 printf("thread_function is running. Argument was %s\n", (char *)arg);//新线程执行打印全局变量 36 sleep(3);//延时 37 strcpy(message, "Bye!");//全局变量赋值为新 38 pthread_exit("Thank you for the CPU time");//线程的返回值 39 }
程序的执行的结果:
1 jason@t61:~/c_program/544977-blp3e/chapter12$ gcc thread1.c -o thread1 -lpthread 2 jason@t61:~/c_program/544977-blp3e/chapter12$ ./thread1 3 Waiting for thread to finish... 4 thread_function is running. Argument was Hello World 5 Thread joined, it returned Thank you for the CPU time 6 Message is now Bye!
新线程修改了字符串数组,旧的线程可以访问这个修改,要是fork出的子进程就不能实现这些。
第二个简单的线程程序:两个线程同时执行
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <pthread.h> 5 6 void *thread_function(void *arg);//定义新线程执行函数的原形 7 int run_now = 1;//定义一个全局变量 8 char message[] = "Hello World";//定义一个全局字符串数组 9 10 int main() 11 { 12 int res; 13 pthread_t a_thread; 14 void *thread_result; 15 int print_count1 = 0; 16 17 res = pthread_create(&a_thread, NULL, thread_function, (void *)message);//创建新的线程 18 if (res != 0) 19 {//失败处理 20 perror("Thread creation failed"); 21 exit(EXIT_FAILURE); 22 } 23 24 while(print_count1++ < 20) {//先执行1的打印,然后改变全局b被设置为2回到while继续循环,睡1秒 25 if (run_now == 1) { 26 printf("1"); 27 run_now = 2; 28 } 29 else { 30 sleep(1); 31 } 32 } 33 34 printf("\nWaiting for thread to finish...\n"); 35 res = pthread_join(a_thread, &thread_result);//等待新创建的线程的结束,打印返回值 36 if (res != 0) { 37 perror("Thread join failed"); 38 exit(EXIT_FAILURE); 39 } 40 printf("Thread joined\n"); 41 exit(EXIT_SUCCESS); 42 } 43 44 void *thread_function(void *arg) 45 {//新线程来执行这个函数,传递的函数是字符串数组指针 46 int print_count2 = 0;//局部变量 47 48 while(print_count2++ < 20) 49 { 50 if (run_now == 2) //新进程会在这里面执行打印2并把全局变量设置为1,继续while然后睡1秒 51 { 52 printf("2"); 53 run_now = 1; 54 } 55 else { 56 sleep(1); 57 } 58 }//最后一次打印2 59 60 sleep(3);//睡3秒之后新线程退出 61 }
程序的执行效果:
1 jason@t61:~/c_program/544977-blp3e/chapter12$ gcc thread2.c -o thread2 -lpthread 2 jason@t61:~/c_program/544977-blp3e/chapter12$ ./thread2 3 1212121212121212121//不知道为什么最后的一个2没有输出,或者有时候在Thread joined之前输出 4 Waiting for thread to finish... 5 Thread joined 6 //总的来说每个线程通过设置run_now变量的方法来通知另一个线程开始运行,然后等待另一个线程 7 //改变变量后再次运行,这个例子显示了两个线程之间自动的交替执行。
线程同步
上面的两个线程同步是通过轮询技术,所以效率很低。
有一组设计好的函数为我们提供了更好的控制线程执行和访问代码临界区域的方法
两种基本的方法:
信号量:作用如同看守一段代码的看门人
互斥量:作用如同保护代码的一个互斥设备
这两个方法看起来是相似的,事实上,它们可以相互通过对方来实现。
但是对于一些情况,可能使用信号量或互斥量中的一个更符合问题的语义,并且效果更好。
例如:
想控制任一时刻只能有一个线程可以访问的一些内存,使用互斥量就要自然的多。
想控制对一组相同对象的访问时,更合适使用计数信号量。
用信号量进行同步:
信号量一般常用来保护一段代码
使其每次只能被一个执行线程运行,要完成这个工作,要使用二进制信号量。
使其每次只能被有限数目的线程运行,就要使用计数信号量。
信号量函数的名字以sem_开头
#include <semaphore.h> int sem_init(sem_t * sem, int pshared, unsigned int value);//完成信号量的创建 /*初始化由sem指向的信号量对象,设置它的共享选项,并设置一个初始的整数值 0表示信号量是当前进程的局部信号量 否则就可以在多个进程之间共享*/
#include <semaphore.h> int sem_wait(sem_t * sem);//以原子操作的方式给信号量的值减1 int sem_post(sem_t * sem);//以原子操作的方式给信号量的值加1 /*这两个函数参数是个指针,指针指向的对象是sem_init初始化的信号量 对于值为0的信号量调用sem_wait,这个函数会等待,直到有其他的信号量的值使其不再是0为止。*/
#include <semaphore.h> int sem_destory(sem_t * sem)//用完信号量之后对它进行清理 /*要是清理的信号量正在被一些线程等待,就会收到一个错误。*/
上面函数,和大多数的函数一样成功的时候返回0
线程信号量的实例:
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <pthread.h> 5 #include <semaphore.h>//包含这个头文件使得我们可以使用信号量 6 7 void *thread_function(void *arg);//定义线程执行的函数 8 sem_t bin_sem; 9 10 #define WORK_SIZE 1024//宏定义一个数值 11 char work_area[WORK_SIZE];//分配一个空的字符数组 12 13 int main() 14 { 15 int res; 16 pthread_t a_thread; 17 void *thread_result; 18 19 res = sem_init(&bin_sem, 0, 0);//创建一个初始值是0的局部信号量 20 if (res != 0) 21 {//错误处理 22 perror("Semaphore initialization failed"); 23 exit(EXIT_FAILURE); 24 } 25 res = pthread_create(&a_thread, NULL, thread_function, NULL);//创建一个新的线程 26 if (res != 0) 27 {//错误处理 28 perror("Thread creation failed"); 29 exit(EXIT_FAILURE); 30 } 31 printf("Input some text. Enter 'end' to finish\n");//创建成功先打印这句话,下面执行函数 32 while(strncmp("end", work_area, 3) != 0) 33 {//区域为空字符串不相等,函数返回不等于0成立,执行循环体 34 fgets(work_area, WORK_SIZE, stdin);//老线程从标准输入读取这么多大小的输入放到指定的地方 35 sem_post(&bin_sem);//给信号量加1,相当于释放信号量 36 } 37 printf("\nWaiting for thread to finish...\n"); 38 res = pthread_join(a_thread, &thread_result);//等待子进程的结束 39 if (res != 0) 40 { 41 perror("Thread join failed"); 42 exit(EXIT_FAILURE); 43 } 44 printf("Thread joined\n"); 45 sem_destroy(&bin_sem); 46 exit(EXIT_SUCCESS); 47 } 48 49 void *thread_function(void *arg) 50 { 51 sem_wait(&bin_sem);//给信号量减一 52 while(strncmp("end", work_area, 3) != 0)//前三个字符一样的话就返回0,如果输入正确,现在是一样 53 { 54 printf("You input %d characters\n", strlen(work_area) -1);//要是出入多的话,新线程统计输入。 55 sem_wait(&bin_sem); 56 //然后信号量减1但是不成功只好等待,老线程会加1,之后唤醒,执行比较,决定循环还是退出 57 } 58 pthread_exit(NULL);//新线程退出 59 }
程序的执行的效果:
1 jason@t61:~/c_program/544977-blp3e/chapter12$ ./thread3 2 Input some text. Enter 'end' to finish 3 hi end 4 You input 6 characters 5 helloend 6 You input 8 characters 7 h 8 You input 1 characters 9 nihao 10 You input 6 characters 11 end 12 13 Waiting for thread to finish... 14 Thread joined
对上面的程序进行部分的修改:
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <pthread.h> 5 #include <semaphore.h> 6 7 void *thread_function(void *arg); 8 sem_t bin_sem; 9 10 #define WORK_SIZE 1024 11 char work_area[WORK_SIZE]; 12 13 int main() { 14 int res; 15 pthread_t a_thread; 16 void *thread_result; 17 18 res = sem_init(&bin_sem, 0, 0); 19 if (res != 0) { 20 perror("Semaphore initialization failed"); 21 exit(EXIT_FAILURE); 22 } 23 res = pthread_create(&a_thread, NULL, thread_function, NULL); 24 if (res != 0) { 25 perror("Thread creation failed"); 26 exit(EXIT_FAILURE); 27 } 28 29 printf("Input some text. Enter 'end' to finish\n"); 30 while(strncmp("end", work_area, 3) != 0) {//输入不是end执行循环体 31 if (strncmp(work_area, "FAST", 4) == 0) {//输入前四个字符是FAST的时候执行 32 sem_post(&bin_sem);//信号量加1释放, 33 strcpy(work_area, "Wheeee...");//拷贝这个字符串给全局字符串,这会导致新线程没时间统计FAST的长度 34 } else { 35 fgets(work_area, WORK_SIZE, stdin); 36 } 37 sem_post(&bin_sem);//这里信号量又加1释放 38 }//新线程等待时候主线程比较,执行while,不执行if获取输入但是信号量加1,这时新线程执行第三次统计 39 40 printf("\nWaiting for thread to finish...\n"); 41 res = pthread_join(a_thread, &thread_result); 42 if (res != 0) { 43 perror("Thread join failed"); 44 exit(EXIT_FAILURE); 45 } 46 printf("Thread joined\n"); 47 sem_destroy(&bin_sem); 48 exit(EXIT_SUCCESS); 49 } 50 51 void *thread_function(void *arg) { 52 sem_wait(&bin_sem);//新线程获得信号量减一 53 while(strncmp("end", work_area, 3) != 0) {//字符串现在不等执行循环体 54 printf("You input %d characters\n", strlen(work_area) -1);//统计输入字符串 55 sem_wait(&bin_sem);//减1之后还不是是1判断while再执行统计,然后等待去减1 56 } 57 pthread_exit(NULL); 58 }
执行的效果:
1 jason@t61:~/c_program/544977-blp3e/chapter12$ ./thread3a 2 Input some text. Enter 'end' to finish 3 hi 4 You input 2 characters 5 FASTTT 6 You input 8 characters 7 You input 8 characters 8 You input 8 characters 9 endend 10 11 Waiting for thread to finish... 12 Thread joined 13 jason@t61:~/c_program/544977-blp3e/chapter12$
为了解决上面的问题 可以再增加一个信号量,让主线程等到统计线程完成字符个数的统计后再继续执行
用互斥量进行同步:
允许程序员锁住某个对象,使得每次只能有一个线程访问它
为了控制对关键代码的访问,必须在进入这段代码之前锁住一个互斥量,然后在完成之后解锁它
1 #include <pthread.h> 2 int pthread_mutex_init(pthread_mutex_t * mutex, const pthread_mutexattr_t * mutexattr); 3 int pthread_mutex_lock(pthread_mutex_t * mutex); 4 int pthread_mutex_unlock(pthread_mutex_t * mutex); 5 int pthread_mutex_destory(pthread_mutex_t * mutex);
成功时返回0失败时返回错误代码不会设置errno所以必须对函数的返回代码进行检查
/*init函数的第二个参数允许我们设置互斥量的属性,默认的属性是fast
但是是有缺点的如果程序试图对一个已经加了锁的互斥量调用了lock函数,程序会被阻塞
而拥有互斥量的这个线程正是现在被阻塞车线程,多以互斥量就永远不会被锁了。程序死锁。
这个问题可以改变互斥量的属性来解决,我们可以让它检查这种情况并返回一个错误
或者让它递归的操作,给同一个线程加上多个锁,但是注意在后面执行同等数量的解锁操作。*/
线程互斥量程序:
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <pthread.h> 5 #include <semaphore.h> 6 7 void *thread_function(void *arg);//定义新线程执行函数 8 pthread_mutex_t work_mutex; //定义一个这个类型的互斥量 9 10 #define WORK_SIZE 1024//定义一个数 11 char work_area[WORK_SIZE];//分配一个内存 12 int time_to_exit = 0;//初始化一个整型为0 13 14 int main() { 15 int res; 16 pthread_t a_thread; 17 void *thread_result; 18 res = pthread_mutex_init(&work_mutex, NULL);//创建一个互斥量到这个参数的地址 19 if (res != 0) {//出错处理 20 perror("Mutex initialization failed"); 21 exit(EXIT_FAILURE); 22 } 23 res = pthread_create(&a_thread, NULL, thread_function, NULL);//创建一个新线程指向这个函数 24 if (res != 0) {//出错处理 25 perror("Thread creation failed"); 26 exit(EXIT_FAILURE); 27 } 28 29 pthread_mutex_lock(&work_mutex);//锁定创建出的互斥量 30 printf("Input some text. Enter 'end' to finish\n");//打印一句话 31 while(!time_to_exit) {//因为非0为真执行循环 //老线程接手继续执行 32 fgets(work_area, WORK_SIZE, stdin);//标准输入一些东西,(加锁读入数据) 33 pthread_mutex_unlock(&work_mutex);//解锁互斥量(解锁统计数据) 34 while(1) {//恒执行周期性执行 35 pthread_mutex_lock(&work_mutex);//尝试锁定互斥量成功的话检查时候要计数 36 if (work_area[0] != '\0') {//输入不为空执行这个循环 37 pthread_mutex_unlock(&work_mutex);//解锁互斥量 38 sleep(1);//睡觉让新线程去执行统计 //不为空解锁交给新线程 39 } 40 else {//输入为空的话就跳出循环此时互斥量还是被锁定的 //为空锁定退出 41 break; 42 } 43 } 44 } 45 pthread_mutex_unlock(&work_mutex); //再接着为空的话对锁定退出执行这个解锁 46 printf("\nWaiting for thread to finish...\n"); 47 res = pthread_join(a_thread, &thread_result);//等待子进程结束 48 if (res != 0) { 49 perror("Thread join failed"); 50 exit(EXIT_FAILURE); 51 } 52 printf("Thread joined\n"); 53 pthread_mutex_destroy(&work_mutex); //销毁互斥量 54 exit(EXIT_SUCCESS); 55 } 56 57 void *thread_function(void *arg) { 58 sleep(1);//先睡1秒,保证老线程先执行 59 pthread_mutex_lock(&work_mutex);//新线程试图锁定这个互斥量,要是已经被锁定就阻塞 60 while(strncmp("end", work_area, 3) != 0) {//不退出也就是不等于end时候执行计数 61 printf("You input %d characters\n", strlen(work_area) -1); 62 work_area[0] = '\0';//给区域赋值为\0 63 pthread_mutex_unlock(&work_mutex);//解锁互斥量 64 sleep(1);//去睡觉让老线程执行 65 pthread_mutex_lock(&work_mutex); //新线程锁定互斥量 66 while (work_area[0] == '\0' ) { //为空传过来 67 pthread_mutex_unlock(&work_mutex); //首先解锁, 68 sleep(1); //然后睡觉 69 pthread_mutex_lock(&work_mutex); //从等待中执行完成回来锁定 70 } 71 } 72 time_to_exit = 1; //要是程序请求退出设置这个为1 73 work_area[0] = '\0'; 74 pthread_mutex_unlock(&work_mutex); //解锁 75 pthread_exit(0); //退出 76 }
程序执行效果:
1 jason@t61:~/c_program/544977-blp3e/chapter12$ ./thread4 2 Input some text. Enter 'end' to finish 3 123 4 You input 3 characters 5 6 You input 0 characters 7 8 You input 0 characters 9 endend 10 11 Waiting for thread to finish... 12 Thread joined
参考文献:Linux程序设计
date:2015年 06月 30日 星期二 13:04:58 CST
万事走心 精益求美