线程
线程ID pthread_t
函数:
比较线程ID是否相等:int pthread_equal(pthread_t tid1,pthread_t tid2);
获取自己的线程ID:pthread_t pthread_self(void)
例子:工作队列,主线程控制作业分配,其他线程从作业队列取出自己的作业。需要比较作业中设置的ID和自己的ID。
创建线程:失败返回错误码,不像其他POSIX函数一样设置errno。所以检测返回值,如果不是0,就说明失败。
#include <pthread.h>
int pthread_create(pthread_t *restrict tidp, //返回线程的ID
const pthread_attr_t *restrict attr, //线程属性
void *(*start_rtn)(void), //线程函数入口地址
void *restrict arg); //线程函数传参数(如果参数一个以上就用这个)
实验:创建线程
1 #include <stdio.h>
2 #include "apue.h"
3 #include <pthread.h>
4
5 void* fun(void *arg)
6 {
7 printf("new thread pid = %lu,tid = %lu\n",(unsigned long)getpid(),(unsigned long)pthread_self());
8 return NULL;
9 }
10
11
12 int main()
13 {
14 pthread_t tid;
15 if(pthread_create(&tid,NULL,fun,NULL)!=0)
16 {
17 printf("creat pthread error\n");
18 exit(1);
19 }
20 sleep(2);
21 printf("main thread pid = %lu,tid = %lu\n",(unsigned long)getpid(),(unsigned long)pthread_self());
22 exit(0);
23
24 }
运行结果:
new thread pid = 12164,tid = 140259601106688
main thread pid = 12164,tid = 140259609409280
注意:新线程的ID在tid中,但是新线程不能用这个,因为新线程在pthread_creat返回之前运行,则tid是未初始化的值。Linux2.6对内核的线程库进行了很大的修改。
线程终止
void pthread_exit(void *retval)
int pthread_join(pthread_t thread, void **retval);//已经在分离态,就会失败,返回EINVAL。
实验:获取线程的退出码
1 #include <stdio.h>
2 #include "apue.h"
3 #include <pthread.h>
4
5 void*fun1(void *p)
6 {
7 printf("I am thread 1\n");
8 return (void*)(1);
9 }
10
11
12 void*fun2(void *p)
13 {
14 printf("I am thread 2\n");
15 pthread_exit((void*)(2));
16 }
17
18
19 int main()
20 {
21 pthread_t tid1,tid2;
22 void *tret;
23 if(pthread_create(&tid1,NULL,fun1,NULL)!=0)
24 {
25 printf("creat thread1 error\n");
26 exit(1);
27 }
28 if(pthread_create(&tid2,NULL,fun2,NULL)!=0)
29 {
30 printf("creat thread2 error\n");
31 exit(2);
32 }
33 if(pthread_join(tid1,&tret)!=0)
34 {
35 printf("join thread1 error\n");
36 exit(3);
37 }
38 printf("thread1 tret = %d\n",(int)tret);
39 if(pthread_join(tid2,&tret)!=0)
40 {
41 printf("join thread2 error\n");
42 exit(4);
43 }
44 printf("thread1 tret = %d\n",(int)tret);
45
46 }
运行结果:
I am thread 1
I am thread 2
thread1 tret = 1
thread1 tret = 2
pthread_cancel取消同一进程其他线程, 只是请求。
pthread_cleanup_push
pthread_cleanup_pop
pthread_detach
(1)如果线程只是由于简单的返回而终止的,则清除函数不会被调用。
(2)如果pthread_cleanup_pop被传递0参数,则清除函数不会被调用,但是会清除处于栈顶的清理函数。
实验:
1 #include <stdio.h>
2 #include <pthread.h>
3 #include "apue.h"
4
5
6 void cleanup(void *arg)
7 {
8 printf("clean:%s\n",(char*)arg);
9 }
10
11 void *fun1(void *arg)
12 {
13 printf("pthread1 start\n");
14 pthread_cleanup_push(cleanup,(void *)"pthread 1 first handler");
15 pthread_cleanup_push(cleanup,(void *)"pthread 1 second handler");
16 printf("thread 1 push complete\n");
17 if(arg)
18 return (void *)1;
19 pthread_cleanup_pop(0);
20 pthread_cleanup_pop(0);
21 return ((void*)1);
22 }
23 void *fun2(void *arg)
24 {
25 printf("pthread2 start\n");
26 pthread_cleanup_push(cleanup,(void *)"pthread 2 first handler");
27 pthread_cleanup_push(cleanup,(void *)"pthread 2 second handler");
28 printf("thread 2 push complete\n");
29 if(arg)//arg为0,就调用pop,则清理函数不会被调用
30 pthread_exit((void *)2);
31 pthread_cleanup_pop(0);//参数为0,不会调用清理函数,为非0会调用
32 pthread_cleanup_pop(0);
33 pthread_exit((void *)2);
34 }
35
36
37 int main()
38 {
39 int err;
40 pthread_t tid1,tid2;
41 void *tret;
42 err = pthread_create(&tid1,NULL,fun1,(void *)1);
43 if(err!=0)
44 {
45 perror("pthread1 creat error");
46 exit(1);
47 }
48 err = pthread_create(&tid2,NULL,fun2,(void *)1);
49 if(err!=0)
50 {
51 perror("pthread2 creat error");
52 exit(2);
53 }
54 err = pthread_join(tid1,&tret);
55 if(err!=0)
56 {
57 perror("pthread1 join error");
58 exit(3);
59 }
60 printf("pthread1 exit code = %ld\n",(long)tret);
61 err = pthread_join(tid2,&tret);
62 if(err!=0)
63 {
64 perror("pthread2 join error");
65 exit(4);
66 }
67 printf("pthread2 exit code = %ld\n",(long)tret);
68 exit(0);
69 }
运行结果:
pthread1 start
thread 1 push complete
pthread2 start
thread 2 push complete
clean:pthread 2 second handler
clean:pthread 2 first handler
pthread1 exit code = 1
pthread2 exit code = 2
线程同步
一个变量可被多个线程修改,就要同步。
互斥量
互斥量本质是一把锁,释放锁之前,如果很多阻塞着,释放后,谁先抢到,谁就可以对互斥量加锁。
#include <pthread.h>
//使用默认属性初始化互斥量,则第二个参数为NULL
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_destroy(pthread_mutex_t *mutex);//动态分配的锁,释放前要调用这个,见实验
实验:使用互斥量保护数据结构
通过增加,减少数据结构的引用计数实现
疑问:APUE的做法,先释放锁,再销毁。(如果释放后,被其他线程锁住,不就会出问题?)
1 #include <stdio.h>
2 #include <pthread.h>
3 #include "apue.h"
4
5 typedef struct foo
6 {
7 int foo_id;
8 pthread_mutex_t f_lock;
9 int foo_count;
10 }myfoo;
11
12
13 myfoo *foo_alloc(int id)
14 {
15 myfoo *ret = (myfoo*)malloc(sizeof(myfoo));
16 ret->foo_count=1;
17 ret->foo_id = id;
18 if(pthread_mutex_init(&ret->f_lock,NULL)!=0)
19 {
20 free(ret);
21 return NULL;
22 }
23 return ret;
24
25 }
26
27 void foo_hold(myfoo *foo)
28 {
29 pthread_mutex_lock(&foo->f_lock);
30 foo->foo_count++;
31 pthread_mutex_unlock(&foo->f_lock);
32 }
33
34 void foo_rele(myfoo *foo)
35 {
36 pthread_mutex_lock(&foo->f_lock);
37 if(--foo->foo_count==0)
38 {
39 printf("destroy foo\n");
40 pthread_mutex_unlock(&foo->f_lock);
41 pthread_mutex_destroy(&foo->f_lock);
42 free(foo);
43 }
44 else
45 pthread_mutex_unlock(&foo->f_lock);
46 }
47
48 int main()
49 {
50 myfoo *foo=foo_alloc(1);
51 foo_hold(foo);
52 foo_hold(foo);
53 foo_hold(foo);
54 printf("%d\n",foo->foo_count);
55 foo_rele(foo);
56 foo_rele(foo);
57 foo_rele(foo);
58 foo_rele(foo);
59 exit(0);
60
61 }
避免死锁
产生死锁原因:
1、线程试图对一个互斥量加锁两次,自身会陷入死锁。
2、两个线程试图锁住对方正锁住的互斥量。
解决:
1、控制加锁顺序:例如总是先锁住A再锁住B。
2、pthre_mutex_trylock:如果不能获取,先释放占有的锁,过一段时间再试。
323页两个实验
函数pthread_mutex_timelock
读写锁(也叫共享互斥锁)
状态:读模式锁,写模式锁,不加锁
适合对数据结构读次数远大于写次数的情况。
类型:pthread_rwlock_t
pthread_rwlock_init
pthread_rwlock_destroy
pthread_rwlock_rdlock
pthread_rwlock_wrlock
pthread_rwlock_unlock
pthread_rwlock_trywrlock
pthread_rwlock_tryunlock
1 #include <stdio.h>
2 #include <pthread.h>
3 #include "apue.h"
4
5 typedef struct job
6 {
7 struct job *prev;
8 struct job *next;
9 pthread_t j_id;
10 }myjob;
11
12
13 typedef struct job_queue
14 {
15 myjob *head;
16 myjob *tail;
17 pthread_rwlock_t q_lock;
18 }myjob_queue;
19
20 int queue_init(myjob_queue *queue)
21 {
22 int err;
23 queue->head=NULL;
24 queue->tail=NULL;
25 err = pthread_rwlock_init(&queue->q_lock,NULL);
26 if(err!=0)
27 return err;
28 else
29 return 0;
30 }
31
32 void queue_insert(myjob_queue *queue,myjob *job)
33 {
34 pthread_rwlock_wrlock(&queue->q_lock);
35 job->next = queue->head;
36 job->prev=NULL;
37 if(queue->head!=NULL)
38 {
39 queue->head->prev=job;
40 }
41 else
42 {
43 queue->tail = job;
44 }
45 queue->head = job;
46 pthread_rwlock_unlock(&queue->q_lock);
47 }
48
49 void queue_append(myjob_queue *queue,myjob *job)
50 {
51 pthread_rwlock_wrlock(&queue->q_lock);
52 job->next = NULL;
53 job->prev=queue->tail;
54 if(queue->tail!=NULL)
55 {
56 queue->tail->next=job;
57 }
58 else
59 {
60 queue->head = job;
61 }
62 queue->tail = job;
63 pthread_rwlock_unlock(&queue->q_lock);
64 }
65
66 void queue_remove(myjob_queue *queue,myjob *job)
67 {
68 pthread_rwlock_wrlock(&queue->q_lock);
69 if(job==queue->head)
70 {
71 queue->head=job->next;
72 if(job==queue->tail)
73 {
74 queue->tail=NULL;
75 }
76 else
77 {
78 job->next->prev=job->prev;
79 }
80 }
81 else if(job==queue->tail)
82 {
83 queue->tail=job->prev;
84 if(job == queue->head)
85 {
86 queue->head=NULL;
87 }
88 else
89 {
90 job->prev->next=job->next;
91 }
92 }
93 else
94 {
95 job->next->prev=job->prev;
96 job->prev->next=job->next;
97 }
98 pthread_rwlock_unlock(&queue->q_lock);
99 }
100
101 myjob *findjob(myjob_queue *queue,pthread_t id)
102 {
103 myjob *ret;
104 if(pthread_rwlock_rdlock(&queue->q_lock)!=0)
105 return NULL;
106 for(ret = queue->head;ret!=NULL;ret = ret->next)
107 {
108 if(ret->j_id==id)
109 {
110 break;
111 }
112 }
113 pthread_rwlock_unlock(&queue->q_lock);
114 return ret;
115
116 }
带有超时的读写锁
条件变量
类型:pthread_cont_t
pthread_cond_init
pthread_cond_destroy
pthread_cond_wait
释放之前锁定的互斥量,然后阻塞,等到被唤醒的时候,锁住互斥量。
pthread_cond_timewait
这些超时的函数用的是绝对时间。因此获取当前时间,并且加上定时的时间就可以。
pthread_cond_signal
pthread_cond_broadcast
实验:生产者消费者模型
1 #include <stdio.h>
2 #include <time.h>
3 #include <pthread.h>
4 #include "apue.h"
5
6 pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
7 pthread_cond_t producted = PTHREAD_COND_INITIALIZER;
8
9 struct node
10 {
11 struct node *next;
12 int date;
13 };
14
15 struct node *head;
16
17 void *productor(void *p)
18 {
19 while(1)
20 {
21 struct node *mp = (struct node *)malloc(sizeof(struct node));
22 pthread_mutex_lock(&lock);
23 mp->date=rand()%100;
24 mp->next = head->next;
25 printf("I product %d\n",mp->date);
26 head->next = mp;
27 pthread_mutex_unlock(&lock);
28 pthread_cond_signal(&producted);
29 sleep(rand()%5);
30 }
31 return NULL;
32 }
33
34 void *consumer(void *p)
35 {
36 struct node *tmp;
37 while(1)
38 {
39 pthread_mutex_lock(&lock);
40 while(head->next==NULL)
41 {
42 pthread_cond_wait(&producted,&lock);
43 }
44 tmp = head->next;
45 head->next = head->next->next;
46 printf("I consum:%d\n",tmp->date);
47 pthread_mutex_unlock(&lock);
48 free(tmp);
49 sleep(rand()%5);
50 }
51 return NULL;
52 }
53
54 int main()
55 {
56 pthread_t tid1,tid2;
57 head = (struct node *)malloc(sizeof(struct node));
58 head->next=NULL;
59 head->date=0;
60 srand((unsigned int)time(NULL));
61 if(pthread_create(&tid1,NULL,productor,NULL)!=0)
62 perror("creat thread 1 failed");
63 if(pthread_create(&tid2,NULL,consumer,NULL)!=0)
64 perror("creat thread 2 failed");
65 if(pthread_join(tid1,NULL)!=0)
66 perror("can't join thread 1");
67 if(pthread_join(tid1,NULL)!=0)
68 perror("can't join thread 2");
69 exit(0);
70 }
自旋锁
自旋锁是专为防止多处理器并发而引入的一种锁,它在内核中大量应用于中断处理等部分。
屏障
协调多个线程同步。
pthread_join也是一种屏障,一个线程等待,直到另一个线程退出。
类型pthread_barrier_t
pthread_barrier_wait等待,等到返回次数等于设置的初始值,就可以继续,否则阻塞。
int pthread_barrier_destroy(pthread_barrier_t *barrier);
int pthread_barrier_init(pthread_barrier_t *restrict barrier,
const pthread_barrierattr_t *restrict attr, unsigned count);
实验:4个线程分别计数10次,全部计数完毕,主线程退出。
1 #include <stdio.h>
2 #include "apue.h"
3 #include <pthread.h>
4
5 pthread_barrier_t t;
6
7
8 void* fun(void *p)
9 {
10 for(int i=1;i<=10;i++)
11 {
12 printf("I am pthread %d:%d\n",(int)p,i);
13 sleep(1);
14 }
15 pthread_barrier_wait(&t);
16 }
17
18 int main()
19 {
20 pthread_t tid;
21 pthread_barrier_init(&t,NULL,5);//设置为6,就永远阻塞住了
22 for(int i=0;i<4;i++)
23 {
24 pthread_create(&tid,NULL,fun,(void*)i);
25 }
26 pthread_barrier_wait(&t);
27 printf("All pthread complete\n");
28 exit(0);
29 }
运行结果
理解:5个线程都处于等待状态,就可以继续,否则阻塞。
I am pthread 0:1
I am pthread 1:1
I am pthread 2:1
I am pthread 3:1
I am pthread 3:2
I am pthread 1:2
I am pthread 0:2
I am pthread 2:2
I am pthread 3:3
I am pthread 1:3
I am pthread 0:3
I am pthread 2:3
I am pthread 3:4
I am pthread 1:4
I am pthread 2:4
I am pthread 0:4
I am pthread 3:5
I am pthread 1:5
I am pthread 2:5
I am pthread 0:5
I am pthread 3:6
I am pthread 1:6
I am pthread 2:6
I am pthread 0:6
I am pthread 3:7
I am pthread 1:7
I am pthread 0:7
I am pthread 2:7
I am pthread 3:8
I am pthread 1:8
I am pthread 0:8
I am pthread 2:8
I am pthread 3:9
I am pthread 1:9
I am pthread 0:9
I am pthread 2:9
I am pthread 3:10
I am pthread 0:10
I am pthread 1:10
I am pthread 2:10
All pthread complete