内容有:线程概述、创建线程、线程退出、回收子线程资源、线程分离、线程取消、设置线程属性、线程同步、互斥锁、死锁、读写锁、条件变量、生产者消费者模型、信号量。
1 线程概述 与进程(process)类似,线程(thread)是允许应用程序并发执行多个任务的一种机 制。一个进程可以包含多个线程。同一个程序中的所有线程均会独立执行相同程序,且共 享同一份全局内存区域,其中包括初始化数据段、未初始化数据段,以及堆内存段。(传 统意义上的 UNIX 进程只是多线程程序的一个特例,该进程只包含一个线程)
进程是 CPU 分配资源的最小单位,线程是操作系统调度执行的最小单位。线程是轻量级的进程(LWP:Light Weight Process),在 Linux 环境下线程的本 质仍是进程。
查看指定进程的 LWP 号:ps –Lf pid
1.1 进程和线程的区别 进程间的信息难以共享。由于除去只读代码段外,父子进程并未共享内存,因此必须采用 一些进程间通信方式,在进程间进行信息交换。
调用 fork() 来创建进程的代价相对较高,即便利用写时复制技术,仍然需要复制诸如 内存页表和文件描述符表之类的多种进程属性,这意味着 fork() 调用在时间上的开销 依然不菲。
线程之间能够方便、快速地共享信息。只需将数据复制到共享(全局或堆)变量中即可。
创建线程比创建进程通常要快 10 倍甚至更多。线程间是共享虚拟地址空间的,无需采 用写时复制来复制内存,也无需复制页表。
1.2 线程之间共享的资源
1.3 NPTL 当 Linux 最初开发时,在内核中并不能真正支持线程。但是它的确可以通过 clone() 系统调用将进程作为可调度的实体。这个调用创建了调用进程(calling process)的 一个拷贝,这个拷贝与调用进程共享相同的地址空间。LinuxThreads 项目使用这个调用 来完成在用户空间模拟对线程的支持。不幸的是,这种方法有一些缺点,尤其是在信号处 理、调度和进程间同步等方面都存在问题。另外,这个线程模型也不符合 POSIX 的要求。
要改进 LinuxThreads,需要内核的支持,并且重写线程库。有两个相互竞争的项目开始 来满足这些要求。一个包括 IBM 的开发人员的团队开展了 NGPT(Next-Generation POSIX Threads)项目。同时,Red Hat 的一些开发人员开展了 NPTL 项目。NGPT 在 2003 年中期被放弃了,把这个领域完全留给了 NPTL。
NPTL,或称为 Native POSIX Thread Library,是 Linux 线程的一个新实现,它 克服了 LinuxThreads 的缺点,同时也符合 POSIX 的需求。与 LinuxThreads 相 比,它在性能和稳定性方面都提供了重大的改进。
查看当前 pthread 库版本:getconf GNU_LIBPTHREAD_VERSION。
1.4 线程操作函数
1.5 创建线程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 #include <stdio.h> #include <pthread.h> #include <string.h> #include <unistd.h> void * callback (void * arg) { printf ("child thread...\n" ); printf ("arg value: %d\n" , *(int *)arg); return NULL ; } int main () { pthread_t tid; int num = 10 ; int ret = pthread_create(&tid, NULL , callback, (void *)&num); if (ret != 0 ) { char * errstr = strerror(ret); printf ("error : %s\n" , errstr); } for (int i = 0 ; i < 5 ; i++) { printf ("%d\n" , i); } sleep(1 ); return 0 ; }
1.6 终止线程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 #include <stdio.h> #include <pthread.h> #include <string.h> void * callback (void * arg) { printf ("child thread id : %ld\n" , pthread_self()); return NULL ; } int main () { pthread_t tid; int ret = pthread_create(&tid, NULL , callback, NULL ); if (ret != 0 ) { char * errstr = strerror(ret); printf ("error : %s\n" , errstr); } for (int i = 0 ; i < 5 ; i++) { printf ("%d\n" , i); } printf ("tid : %ld, main thread id : %ld\n" , tid ,pthread_self()); pthread_exit(NULL ); printf ("main thread exit\n" ); return 0 ; }
1.7 回收已终止的线程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 #include <stdio.h> #include <pthread.h> #include <string.h> #include <unistd.h> int value = 10 ;void * callback (void * arg) { printf ("child thread id : %ld\n" , pthread_self()); pthread_exit((void *)&value); } int main () { pthread_t tid; int ret = pthread_create(&tid, NULL , callback, NULL ); if (ret != 0 ) { char * errstr = strerror(ret); printf ("error : %s\n" , errstr); } for (int i = 0 ; i < 5 ; i++) { printf ("%d\n" , i); } printf ("tid : %ld, main thread id : %ld\n" , tid ,pthread_self()); int * thread_retval; ret = pthread_join(tid, (void **)&thread_retval); if (ret != 0 ) { char * errstr = strerror(ret); printf ("error : %s\n" , errstr); } printf ("exit data : %d\n" , *thread_retval); printf ("回收子线程资源成功!\n" ); pthread_exit(NULL ); return 0 ; }
1.8 线程的分离 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 #include <stdio.h> #include <pthread.h> #include <string.h> #include <unistd.h> void * callback (void * arg) { printf ("chid thread id : %ld\n" , pthread_self()); return NULL ; } int main () { pthread_t tid; int ret = pthread_create(&tid, NULL , callback, NULL ); if (ret != 0 ) { char * errstr = strerror(ret); printf ("error1 : %s\n" , errstr); } printf ("tid : %ld, main thread id : %ld\n" , tid, pthread_self()); ret = pthread_detach(tid); if (ret != 0 ) { char * errstr = strerror(ret); printf ("error2 : %s\n" , errstr); } pthread_exit(NULL ); return 0 ; }
1.9 线程取消 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include <stdio.h> #include <pthread.h> #include <string.h> #include <unistd.h> void * callback (void * arg) { printf ("chid thread id : %ld\n" , pthread_self()); for (int i = 0 ; i < 5 ; i++) { printf ("child : %d\n" , i); } return NULL ; } int main () { pthread_t tid; int ret = pthread_create(&tid, NULL , callback, NULL ); if (ret != 0 ) { char * errstr = strerror(ret); printf ("error1 : %s\n" , errstr); } pthread_cancel(tid); for (int i = 0 ; i < 5 ; i++) { printf ("%d\n" , i); } printf ("tid : %ld, main thread id : %ld\n" , tid, pthread_self()); pthread_exit(NULL ); return 0 ; }
1.10 线程属性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 #include <stdio.h> #include <pthread.h> #include <string.h> #include <unistd.h> void * callback (void * arg) { printf ("chid thread id : %ld\n" , pthread_self()); return NULL ; } int main () { pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); pthread_t tid; int ret = pthread_create(&tid, &attr, callback, NULL ); if (ret != 0 ) { char * errstr = strerror(ret); printf ("error1 : %s\n" , errstr); } size_t size; pthread_attr_getstacksize(&attr, &size); printf ("thread stack size : %ld\n" , size); printf ("tid : %ld, main thread id : %ld\n" , tid, pthread_self()); pthread_attr_destroy(&attr); pthread_exit(NULL ); return 0 ; }
2 线程同步 线程的主要优势在于,能够通过全局变量来共享信息。不过,这种便捷的共享是有代价的:必须确保多个线程不会同时修改同一变量,或者某一线程不会读取正在由其他线程修改的变量。
临界区是指访问某一共享资源的代码片段,并且这段代码的执行应为原子操作,也就是 时访问同一共享资源的其他线程不应终端该片段的执行。
线程同步:即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作,而其他线程则处于等待状态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include <stdio.h> #include <pthread.h> #include <unistd.h> int tickets = 100 ;void * sellticket (void * arg) { while (tickets > 0 ) { usleep(6000 ); printf ("%ld 正在卖第 %d 张门票\n" , pthread_self(), tickets); tickets--; } return NULL ; } int main () { pthread_t tid1, tid2, tid3; pthread_create(&tid1, NULL , sellticket, NULL ); pthread_create(&tid2, NULL , sellticket, NULL ); pthread_create(&tid3, NULL , sellticket, NULL ); pthread_join(tid1, NULL ); pthread_join(tid2, NULL ); pthread_join(tid3, NULL ); pthread_exit(NULL ); return 0 ; }
2.1 互斥锁 为避免线程更新共享变量时出现问题,可以使用互斥量(mutex 是 mutual exclusion 的缩写)来确保同时仅有一个线程可以访问某项共享资源。可以使用互斥量来保证对任意共 享资源的原子访问。
互斥量有两种状态:已锁定(locked)和未锁定(unlocked)。任何时候,至多只有一 个线程可以锁定该互斥量。试图对已经锁定的某一互斥量再次加锁,将可能阻塞线程或者报 错失败,具体取决于加锁时使用的方法。
一旦线程锁定互斥量,随即成为该互斥量的所有者,只有所有者才能给互斥量解锁。一般情 况下,对每一共享资源(可能由多个相关变量组成)会使用不同的互斥量,每一线程在访问 同一资源时将采用如下协议: 针对共享资源锁定互斥量 ;访问共享资源 ;对互斥量解锁。
如果多个线程试图执行这一块代码(一个临界区),事实上只有一个线程能够持有该互斥 量(其他线程将遭到阻塞),即同时只有一个线程能够进入这段代码区域,如下图所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 #include <stdio.h> #include <pthread.h> #include <unistd.h> int tickets = 1000 ;pthread_mutex_t mutex;void * sellticket (void * arg) { while (1 ) { pthread_mutex_lock(&mutex); if (tickets > 0 ) { usleep(6000 ); printf ("%ld 正在卖第 %d 张门票\n" , pthread_self(), tickets); tickets--; }else { pthread_mutex_unlock(&mutex); break ; } pthread_mutex_unlock(&mutex); } return NULL ; } int main () { pthread_mutex_init(&mutex, NULL ); pthread_t tid1, tid2, tid3; pthread_create(&tid1, NULL , sellticket, NULL ); pthread_create(&tid2, NULL , sellticket, NULL ); pthread_create(&tid3, NULL , sellticket, NULL ); pthread_join(tid1, NULL ); pthread_join(tid2, NULL ); pthread_join(tid3, NULL ); pthread_exit(NULL ); pthread_mutex_destroy(&mutex); return 0 ; }
2.2 死锁 有时,一个线程需要同时访问两个或更多不同的共享资源,而每个资源又都由不同的互 斥量管理。当超过一个线程加锁同一组互斥量时,就有可能发生死锁。两个或两个以上的进程在执行过程中,因争夺共享资源而造成的一种互相等待的现象, 若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁。
死锁的几种场景: 忘记释放锁; 重复加锁; 多线程多锁,抢占锁资源。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 #include <stdio.h> #include <pthread.h> #include <unistd.h> pthread_mutex_t mutex1, mutex2;void * workA (void * arg) { pthread_mutex_lock(&mutex1); sleep(1 ); pthread_mutex_lock(&mutex2); printf ("workA....\n" ); pthread_mutex_unlock(&mutex2); pthread_mutex_unlock(&mutex1); return NULL ; } void * workB (void * arg) { pthread_mutex_lock(&mutex2); sleep(1 ); pthread_mutex_lock(&mutex1); printf ("workB....\n" ); pthread_mutex_unlock(&mutex1); pthread_mutex_unlock(&mutex2); return NULL ; } int main () { pthread_mutex_init(&mutex1, NULL ); pthread_mutex_init(&mutex2, NULL ); pthread_t tid1, tid2; pthread_create(&tid1, NULL , workA, NULL ); pthread_create(&tid2, NULL , workB, NULL ); pthread_join(tid1, NULL ); pthread_join(tid2, NULL ); pthread_mutex_destroy(&mutex1); pthread_mutex_destroy(&mutex2); return 0 ; }
2.3 读写锁 当有一个线程已经持有互斥锁时,互斥锁将所有试图进入临界区的线程都阻塞住。但是考 虑一种情形,当前持有互斥锁的线程只是要读访问共享资源,而同时有其它几个线程也想 读取这个共享资源,但是由于互斥锁的排它性,所有其它线程都无法获取锁,也就无法读 访问共享资源了,但是实际上多个线程同时读访问共享资源并不会导致问题。
在对数据的读写操作中,更多的是读操作,写操作较少,例如对数据库数据的读写应用。 为了满足当前能够允许多个读出,但只允许一个写入的需求,线程提供了读写锁来实现。
读写锁的特点:如果有其它线程读数据,则允许其它线程执行读操作,但不允许写操作。如果有其它线程写数据,则其它线程都不允许读、写操作。写是独占的,写的优先级高。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 #include <stdio.h> #include <pthread.h> #include <unistd.h> int num = 1 ;pthread_rwlock_t rwlock;void * writeNum (void * arg) { while (1 ) { pthread_rwlock_wrlock(&rwlock); num++; printf ("++write, tid : %ld, num : %d\n" , pthread_self(), num); pthread_rwlock_unlock(&rwlock); usleep(100 ); } return NULL ; } void * readNum (void * arg) { while (1 ) { pthread_rwlock_rdlock(&rwlock); printf ("===read, tid : %ld, num : %d\n" , pthread_self(), num); pthread_rwlock_unlock(&rwlock); usleep(100 ); } return NULL ; } int main () { pthread_rwlock_init(&rwlock, NULL ); pthread_t wtids[3 ], rtids[5 ]; for (int i = 0 ; i < 3 ; i++) { pthread_create(&wtids[i], NULL , writeNum, NULL ); } for (int i = 0 ; i < 5 ; i++) { pthread_create(&rtids[i], NULL , readNum, NULL ); } for (int i = 0 ; i < 3 ; i++) { pthread_detach(wtids[i]); } for (int i = 0 ; i < 5 ; i++) { pthread_detach(rtids[i]); } pthread_exit(NULL ); pthread_rwlock_destroy(&rwlock); return 0 ; }
2.4 生产者和消费者模型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 #include <stdio.h> #include <pthread.h> #include <stdlib.h> #include <unistd.h> pthread_mutex_t mutex;struct Node { int num; struct Node *next ; }; struct Node * head = NULL ;void * producer (void * arg) { while (1 ) { pthread_mutex_lock(&mutex); struct Node * newNode = (struct Node *)malloc (sizeof (struct Node)); newNode->next = head; head = newNode; newNode->num = rand() % 1000 ; printf ("add node, num : %d, tid : %ld\n" , newNode->num, pthread_self()); pthread_mutex_unlock(&mutex); usleep(100 ); } return NULL ; } void * customer (void * arg) { while (1 ) { pthread_mutex_lock(&mutex); struct Node * tmp = head; if (head != NULL ) { head = head->next; printf ("del node, num : %d, tid : %ld\n" , tmp->num, pthread_self()); free (tmp); pthread_mutex_unlock(&mutex); usleep(100 ); } else { pthread_mutex_unlock(&mutex); } } return NULL ; } int main () { pthread_mutex_init(&mutex, NULL ); pthread_t ptids[5 ], ctids[5 ]; for (int i = 0 ; i < 5 ; i++) { pthread_create(&ptids[i], NULL , producer, NULL ); pthread_create(&ctids[i], NULL , customer, NULL ); } for (int i = 0 ; i < 5 ; i++) { pthread_detach(ptids[i]); pthread_detach(ctids[i]); } while (1 ) { sleep(10 ); } pthread_mutex_destroy(&mutex); pthread_exit(NULL ); return 0 ; }
2.5 条件变量 跟互斥锁一起使用,容量为空的时候,则阻塞,一直等待生产者生产之后才继续。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 #include <stdio.h> #include <pthread.h> #include <stdlib.h> #include <unistd.h> pthread_mutex_t mutex;pthread_cond_t cond;struct Node { int num; struct Node *next ; }; struct Node * head = NULL ;void * producer (void * arg) { while (1 ) { pthread_mutex_lock(&mutex); struct Node * newNode = (struct Node *)malloc (sizeof (struct Node)); newNode->next = head; head = newNode; newNode->num = rand() % 1000 ; printf ("add node, num : %d, tid : %ld\n" , newNode->num, pthread_self()); pthread_cond_signal(&cond); pthread_mutex_unlock(&mutex); usleep(100 ); } return NULL ; } void * customer (void * arg) { while (1 ) { pthread_mutex_lock(&mutex); struct Node * tmp = head; if (head != NULL ) { head = head->next; printf ("del node, num : %d, tid : %ld\n" , tmp->num, pthread_self()); free (tmp); pthread_mutex_unlock(&mutex); usleep(100 ); } else { pthread_cond_wait(&cond, &mutex); pthread_mutex_unlock(&mutex); } } return NULL ; } int main () { pthread_mutex_init(&mutex, NULL ); pthread_cond_init(&cond, NULL ); pthread_t ptids[5 ], ctids[5 ]; for (int i = 0 ; i < 5 ; i++) { pthread_create(&ptids[i], NULL , producer, NULL ); pthread_create(&ctids[i], NULL , customer, NULL ); } for (int i = 0 ; i < 5 ; i++) { pthread_detach(ptids[i]); pthread_detach(ctids[i]); } while (1 ) { sleep(10 ); } pthread_mutex_destroy(&mutex); pthread_cond_destroy(&cond); pthread_exit(NULL ); return 0 ; }
2.6 信号量/信号灯 和互斥锁一起使用。互斥锁保证原子性,即一次只能有一个线程。信号量保证范围,即最多生产多少个,消费多少个。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 #include <stdio.h> #include <pthread.h> #include <stdlib.h> #include <unistd.h> #include <semaphore.h> pthread_mutex_t mutex;sem_t psem;sem_t csem;struct Node { int num; struct Node *next ; }; struct Node * head = NULL ;void * producer (void * arg) { while (1 ) { sem_wait(&psem); pthread_mutex_lock(&mutex); struct Node * newNode = (struct Node *)malloc (sizeof (struct Node)); newNode->next = head; head = newNode; newNode->num = rand() % 1000 ; printf ("add node, num : %d, tid : %ld\n" , newNode->num, pthread_self()); pthread_mutex_unlock(&mutex); sem_post(&csem); } return NULL ; } void * customer (void * arg) { while (1 ) { sem_wait(&csem); pthread_mutex_lock(&mutex); struct Node * tmp = head; head = head->next; printf ("del node, num : %d, tid : %ld\n" , tmp->num, pthread_self()); free (tmp); pthread_mutex_unlock(&mutex); sem_post(&psem); } return NULL ; } int main () { pthread_mutex_init(&mutex, NULL ); sem_init(&psem, 0 , 8 ); sem_init(&csem, 0 , 0 ); pthread_t ptids[5 ], ctids[5 ]; for (int i = 0 ; i < 5 ; i++) { pthread_create(&ptids[i], NULL , producer, NULL ); pthread_create(&ctids[i], NULL , customer, NULL ); } for (int i = 0 ; i < 5 ; i++) { pthread_detach(ptids[i]); pthread_detach(ctids[i]); } while (1 ) { sleep(10 ); } pthread_mutex_destroy(&mutex); pthread_exit(NULL ); return 0 ; }