1. 线程的标识 pthread_t
对于每一个进程都有唯一PID号与之对应,而对于线程而言,也有类似的tid号,即一个pthread_t类型的变量。线程号是表示线程的唯一标识,但是对于线程号而言,其仅仅在其所属的进程上下文中才有意义。
获取当前线程的线程号:
1 2
| #include <pthread.h> pthread_t pthread_self(void);
|
2. 线程的创建
1
| int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
|
- 该函数第一个参数为 pthread_t 指针,用来保存新建线程的线程号;
- 第二个参数表示了线程的属性,一般传入 NULL 表示默认属性;
- 第三个参数是一个函数指针,就是线程执行的函数。这个函数返回值为 void*, 形参为 void*。
- 第四个参数则表示为向线程处理函数传入的参数,若不传入,可用 NULL 填充
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #include <stdio.h> #include <pthread.h> #include <unistd.h>
static void* my_thread_func(void* arg) { printf("pthread_New = %lu\n",(unsigned long)pthread_self()); }
int main(int argc, char *argv[]) { pthread_t tid; int iRet;
iRet = pthread_create(&tid, NULL, my_thread_func, NULL); if(iRet) { printf("pthread_create err!\n"); return -1; } printf("tid_main = %lu tid_new = %lu \n",(unsigned long)pthread_self(),(unsigned long)tid); sleep(1); return 0; }
|
分配资源是以进程为单位的,调度是以线程为单位的,上述代码中主线程和创建出的线程执行的顺序是随机调度的
2.1 向线程传入参数
2.1.1 值传递和地址传递
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
| #include <stdio.h> #include <unistd.h> #include <pthread.h>
static void* func1(void* arg) { while(1) { printf("%s:a = %d Addr = %p \n", __FUNCTION__, *(int *)arg, arg); sleep(1); } } static void* func2(void* arg) { while(1) { printf("%s:a = %ld Addr = %p \n", __FUNCTION__, (long)arg, arg); sleep(1); } }
int main(int argc, char **argv) { pthread_t tid1, tid2; int a = 5, iRet; iRet = pthread_create(&tid1, NULL, func1, (void *)&a); if(iRet != 0) { printf("Error: pthread_create\n"); return -1; } iRet = pthread_create(&tid2, NULL, func2, (void *)(long)a); if(iRet != 0) { printf("Error: pthread_create\n"); return -1; } while (1) { a++; sleep(1); printf("%s:a = %d Addr = %p \n", __FUNCTION__, a, &a); } return 0; }
|
- func1对应地址传递,比较常用好理解
- func2对应值传递:
pthread_create(&tid2, NULL, func2, (void *)(long)a);:先将a转成long类型(8byte),则指针void *arg的值为a=5,即指针指向地址5
printf("%s:a = %d Addr = %p \n", __FUNCTION__, (int)(long)arg, arg);:指针转换为long类型,值为5。
因此,不难理解,部分输出如下:
1 2 3 4 5
| func1:a = 5 Addr = 0x7fff2dcd2eb0 func2:a = 5 Addr = 0x5 main:a = 6 Addr = 0x7fff2dcd2eb0 func1:a = 7 Addr = 0x7fff2dcd2eb0 func2:a = 5 Addr = 0x5
|
2.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
| #include <stdio.h> #include <unistd.h> #include <pthread.h>
struct Stu{ int iId; char cName[32]; float fMark; }; static void* func1(void* arg) { struct Stu *tmp = (struct Stu*)arg; printf("%s:Id = %d Name = %s Mark = %.2f\n", __FUNCTION__, tmp->iId, tmp->cName, tmp->fMark); }
int main(int argc, char **argv) { pthread_t tid; int iRet; struct Stu stu = {1000, "lili", 55};
iRet = pthread_create(&tid, NULL, func1, (void *)&stu); if(iRet != 0) { printf("Error: pthread_create\n"); return -1; } printf("%s:Id = %d Name = %s Mark = %.2f\n", __FUNCTION__, stu.iId, stu.cName, stu.fMark); sleep(1); return 0; }
|
3. 线程的退出与回收
线程的退出情况有三种:
第一种是进程结束,进程中所有的线程也会随之结束。
第二种是通过函数 pthread_exit 来主动的退出线程。
第三种被其他线程调用 pthread_cancel 来被动退出。
当线程结束后,主线程可以通过函数 pthread_join/pthread_tryjoin_np 来回收线程的资源,并且获得线程结束后需要返回的数据。
3.1 线程退出
(1) 线程主动退出
1
| void pthread_exit(void *retval);
|
pthread_exit 函数为线程退出函数,在退出时候可以传递一个 void*类型的数据带给主线程,若选择不传出数据,可将参数填充为 NULL。
(2) 线程被动退出
1
| int pthread_cancel(pthread_t thread);
|
该函数传入一个 tid 号,会强制退出该 tid 所指向的线程,若成功执行会返回 0。
示例代码:
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
| #define _GNU_SOURCE #include <pthread.h> #include <stdio.h> #include <unistd.h>
void* func1(void *arg) { printf("Pthread:1 Come !\n"); while(1) { sleep(1); } }
void* func2(void *arg) { printf("Pthread:2 Come !\n"); pthread_cancel((pthread_t)arg); pthread_exit(NULL); }
int main(int argc, char **argv) { pthread_t tid[2]; int i, iRet, flag = 0;
iRet = pthread_create(&tid[0], NULL, func1, NULL); if(iRet) { perror("pthread_create"); return -1; } iRet = pthread_create(&tid[1], NULL, func2, (void *)tid[0]); if(iRet) { perror("pthread_create"); return -1; }
while(1) { for(i = 0; i < 2; i++) if(pthread_tryjoin_np(tid[i], NULL) == 0) { flag++; printf("Pthread:%d back !\n", i + 1); } if(flag == 2) break; } return 0; }
|
第一个的线程执行死循环睡眠逻辑,理论上除非进程结束,其永远不会结束,但在第二个线程中调用了 pthread_cancel 函数,相当于向该线程发送一个退出的指令,导致线程被退出,最终资源被非阻塞回收掉。
3.2 线程资源回收
(1) 线程资源回收(阻塞方式)
1
| int pthread_join(pthread_t thread, void **retval);
|
该函数为线程回收函数,默认状态为阻塞状态,直到成功回收线程后才返回。第一个参数为要回收线程的 tid 号,第二个参数为线程回收后接受线程传出的数据。
(2) 线程资源回收(非阻塞方式)
1
| int pthread_tryjoin_np(pthread_t thread, void **retval);
|
该函数为非阻塞模式回收函数,通过返回值判断是否回收掉线程,成功回收则返回 0,其余参数与 pthread_join 一致。
3.2.3 示例代码
(1) 示例代码1:
通过阻塞方式回收线程资源
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
| #include <pthread.h> #include <stdio.h>
void* func1(void *arg) { static int tmp = 0; printf("%s:a = %d Addr = %p \n", __FUNCTION__, *(int *)arg, arg); tmp = *(int *) arg + 100; pthread_exit((void *) &tmp); }
int main(int argc, char **argv) { pthread_t tid; int iRet, a = 100; void *vTmp = NULL;
iRet = pthread_create(&tid, NULL, func1, (void *)&a); if(iRet) { perror("pthread_create"); return -1; } pthread_join(tid, &vTmp); printf("%s:Addr = %p Val = %d\n",__FUNCTION__,vTmp,*(int *)vTmp);
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
| #define _GNU_SOURCE #include <pthread.h> #include <stdio.h> #include <unistd.h>
void* func1(void *arg) { printf("Pthread:%ld Come !\n", (long)arg); pthread_exit(arg); }
int main(int argc, char **argv) { pthread_t tid[3]; int i, iRet, flag = 0; void *vTmp = NULL;
for(i = 1; i <= 3; i++) { iRet = pthread_create(&tid[i - 1], NULL, func1, (void *)(long)i); if(iRet) { perror("pthread_create"); return -1; } } while(1) { for(i = 1; i <= 3; i++) { if(pthread_tryjoin_np(tid[i - 1], &vTmp) == 0) { flag++; printf("Pthread:%ld back !\n", (long)vTmp); } } if(flag == 3) break; } return 0; }
|
通过阻塞方式回收线程几乎规定了线程回收的顺序,若最先回收的线程未退出,则一直会被阻塞,导致后续先退出的线程无法及时的回收。
通过函数 pthread_tryjoin_np,使用非阻塞回收,线程可以根据退出先后顺序及时进行资源的回收。
4. 线程的控制
4.1 互斥锁
多个线程都要访问某个临界资源,比如某个全局变量时,需要互斥地进行访问。
(1) 初始化互斥量
1
| int pthread_mutex_init(phtread_mutex_t *mutex, const pthread_mutexattr_t *restrict attr);
|
第一个参数是改互斥量指针,第二个参数为控制互斥量的属性,一般为 NULL。当函数成功后会返回 0,代表初始化互斥量成功。
互斥量的初始化也可以调用宏快速初始化:
1
| pthread_mutex_t mutex = PTHREAD_MUTEX_INITALIZER;
|
(2) 互斥量加锁/解锁
1 2 3
| int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_trylock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex);
|
成功后会返回 0。当某一个线程获得了执行权后,执行 lock 函数,一旦加锁成功后,其余线程遇到 lock 函数时候会发生阻塞,直至获取资源的线程执行 unlock 函数后。 unlock 函数会唤醒其他正在等待互斥量的线程。
(3) 销毁互斥量
1
| int pthread_mutex_destory(pthread_mutex_t *mutex);
|
成功后会返回 0。
(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
| #define _GNU_SOURCE #include <pthread.h> #include <stdio.h> #include <unistd.h>
pthread_mutex_t mutex; int iNum;
void* func1(void *arg) { pthread_mutex_lock(&mutex); while(iNum < 3) { iNum++; printf("%s:iNum = %d \n", __FUNCTION__, iNum); } pthread_mutex_unlock(&mutex); pthread_exit(NULL); }
int main(int argc, char **argv) { pthread_t tid; int i, iRet;
iRet = pthread_mutex_init(&mutex, NULL); if(iRet != 0) { perror("pthread_mutex_init"); return -1; }
iRet = pthread_create(&tid, NULL, func1, NULL); if(iRet) { perror("pthread_create"); return -1; }
pthread_mutex_lock(&mutex); while(iNum > -3) { iNum--; printf("%s:iNum = %d \n", __FUNCTION__, iNum); } pthread_mutex_unlock(&mutex); pthread_join(tid, NULL); pthread_mutex_destroy(&mutex); return 0; }
|
4.2 信号量
信号量跟互斥量不一样,互斥量用来防止多个线程同时访问某个临界资源。信号量起通知作用,线程 A 在等待某件事,线程 B 完成了这件事后就可以给线程 A 发信号
(1) 初始化信号量
1 2
| #include <semaphore.h> int sem_init(sem_t *sem,int pshared,unsigned int value);
|
- 该函数可以初始化一个信号量,第一个参数传入 sem_t 类型指针;
- 第二个参数传入 0 代表线程控制,否则为进程控制;
- 第三个参数表示信号量的初始值,0 表示没有资源,正数表示资源的个数,不允许为负数。
- 待初始化结束信号量后,若执行成功会返回 0。
(2) 信号量PV操作
1 2 3
| int sem_wait(sem_t *sem); int sem_trywait(sem_t *sem); int sem_post(sem_t *sem);
|
sem_wait 函数作用为检测指定信号量是否有资源可用,若无资源可用会阻塞等待,若有资源可用会自动的执行”sem-1”的操作。所谓的”sem-1”是与上述初始化函数中第三个参数值一致,成功执行会返回 0
sem_trywait:此函数是信号量申请资源的非阻塞函数,功能与 sem_wait 一致。
sem_post 函数会释放指定信号量的资源,执行”sem+1”操作。 通过以上 2 个函数可以完成所谓的 PV 操作,即信号量的申请与释放,完成 对线程执行顺序的控制。
(3) 信号量销毁
1
| int sem_destory(sem_t *sem);
|
该函数为信号量销毁函数,执行过后可将信号量进行销毁。成功时返回0。
(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
| #define _GNU_SOURCE #include <pthread.h> #include <stdio.h> #include <unistd.h> #include <semaphore.h>
sem_t sem[3]; void* func1(void *arg) { sem_wait(&sem[0]); printf("%s executed\n", __FUNCTION__); sem_post(&sem[1]); pthread_exit(NULL); }
void* func2(void *arg) { sem_wait(&sem[1]); printf("%s executed\n", __FUNCTION__); sem_post(&sem[2]); pthread_exit(NULL); }
void* func3(void *arg) { sem_wait(&sem[2]); printf("%s executed\n", __FUNCTION__); pthread_exit(NULL); }
int main(int argc, char **argv) { pthread_t tid[3]; int i, iRet, flag = 0;
sem_init(&sem[0], 0, 1); sem_init(&sem[1], 0, 0); sem_init(&sem[2], 0, 0); pthread_create(&tid[0], NULL, func1, NULL); pthread_create(&tid[1], NULL, func2, NULL); pthread_create(&tid[2], NULL, func3, NULL);
for (i = 0; i < 3; i++) { pthread_join(tid[i], NULL); sem_destroy(&sem[i]); } return 0; }
|
4.3 条件变量
条件变量时一种同步机制,用来通知其他线程条件满足了。一般是用来通知对方共享数据的状态信息,因此条件变量时结合互斥量来使用的。
(1) 初始化
1
| int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
|
- 该函数可以初始化一个条件变量,第一个参数传入 pthread_cond_t类型指针;
- 第二个参数传入控制条件变量的属性,一般为 NULL。
- 待初始化结束信号量后,若执行成功会返回 0。
可以通过如下宏直接初始化条件变量:
1
| pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
|
(2) wait/signal
1 2
| int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); int pthread_cond_signal(pthread_cond_t *cond);
|
pthread_cond_wait函数需要结合互斥量一起使用:
1 2 3 4 5 6 7 8
| pthread_mutex_lock(&g_tMutex);
pthread_cond_wait(&g_tConVar, &g_tMutex);
pthread_mutex_unlock(&g_tMutex);
|
如果条件满足,则继续往下执行;如果条件不满足,则会释放互斥量,阻塞直到条件满足并且可以获得互斥锁时,继续往下执行。
(3) 销毁条件变量
1
| int pthread_cond_destroy(pthread_cond_t *cond);
|
成功时返回0。
(4) 示例代码
在主线程中从stdin中接收输入,并在创建的线程中进行打印
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
| #include <pthread.h> #include <stdio.h> #include <unistd.h> #include <semaphore.h> #include <string.h>
static char g_buf[1000]; static pthread_mutex_t g_tMutex = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t g_tConVar = PTHREAD_COND_INITIALIZER;
static void *my_thread_func (void *data) { while (1) { pthread_mutex_lock(&g_tMutex); pthread_cond_wait(&g_tConVar, &g_tMutex);
printf("recv: %s\n", g_buf); pthread_mutex_unlock(&g_tMutex); }
return NULL; }
int main(int argc, char **argv) { pthread_t tid; int ret; char buf[1000]; ret = pthread_create(&tid, NULL, my_thread_func, NULL);
while (1) { fgets(buf, 1000, stdin); pthread_mutex_lock(&g_tMutex); memcpy(g_buf, buf, 1000); pthread_cond_signal(&g_tConVar); pthread_mutex_unlock(&g_tMutex); } return 0; }
|
5.总结
5.1 线程使用流程图

5.2 互斥量使用流程图

5.3 信号量使用流程图
