线程概念
线程:Linux下,线程又被称为轻量级进程,是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。线程是进程中的一个实体,是被系统独立调度和分派的基本单元。线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其他线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。
线程状态:
进程和线程的关系
(1)一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程是操作系统可识别的最小执行和调度单位。
(2)资源分配给进程,同一进程的所有线程共享该进程的所有资源。 同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量。
(3)处理机分给线程,即真正在处理机上运行的是线程。
(4)线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
创建线程
POSIX线程库,链接这些线程函数时要使用“-lpthread”选项。
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
代码如下;
5 void *thr_start(void *arg)
6 {
7 while(1)
8 {
9 printf("I'm calling\n");
10 sleep(1);
11 }
12 return NULL;
13 }
14 int main()
15 {
16 pthread_t tid;
17 //pthread_create创建线程,线程入口函数为thr_start
18 int ret=pthread_create(&tid,NULL,thr_start,NULL);
19 if(ret!=0)
20 perror("pthread_create"),exit(1);
21 while(1)
22 {
23 printf("i'm playing game\n");
24 sleep(1);
25 }
26 return 0;
27 }
用ps - eLf查看:
多线程的进程,又被称为线程组,线程组内的每一个线程在内核之中都存在一个进程描述符(task_struct)与之对应。进程描述符结构体中的pid,对应的是线程ID(LWP);进程描述符中tgid(主线程pid),对应用户层面的进程ID.。
线程终止
void pthread_exit(void *retval);
注意:pthread_exit 或 return 返回的指针所指向的内存单元必须是全局的或者用malloc分配的,不能在线程函数的栈上分配,因为当其他线程得到这个返回指针时线程函数已经退出了。
线程取消:
int pthread_cancel(pthread_t thread);
线程等待和分离
线程等待:int pthread_join(pthread_t thread, void **retval);
线程分离:int pthread_detach(pthread_t thread);
线程的返回值
- 线程通过return返回, retval所指向的单元里存放的是thread线程函数的返回值;
- 通过调用pthread_cancel异常终止,retval所指向的单元里存放的是常数-1(PTHREAD_CANCELED).
- 调用pthread_exit终止,retval所指向的单元里存放的是传给pthread_exit的参数。
代码如下:
6 void *thread1(void *arg)
7 {
8 printf("thread 1 return \n");
9 int *p=(int *)malloc(sizeof(int));
10 *p=1;
11 return (void *)p;
12 }
13 void *thread2(void *arg)
14 {
15 printf("thread 2 exit \n");
16 int *p=(int *)malloc(sizeof(int));
17
18 *p=2;
19 pthread_exit((void *)p);
20 }
21 void *thread3(void *arg)
22 {
23 while(1)
24 {
25 printf("thread 3 run \n");
26 sleep(1);
27 }
28 return NULL;
29 }
30 int main()
31 {
32 pthread_t tid;
33 void *ret;
34
35 //thread 1 return
36 pthread_create(&tid,NULL,thread1,NULL);
37 pthread_join(tid,&ret);
38 printf("thread return ,thread id %x,return code:%d\n",tid,*(int *)ret);
39 free(ret);
40
41 //thread 2 exit
42 pthread_create(&tid,NULL,thread2,NULL);
43 pthread_join(tid,&ret);
44 printf("thread return ,thread id %x,return code:%d\n",tid,*(int *)ret);
45 free(ret);
46
47 //thread 3 cancel by other
48 pthread_create(&tid,NULL,thread3,NULL);
49 sleep(3);
50 pthread_cancel(tid);
51 pthread_join(tid,&ret);
52 if(ret==-1)
53 {
54 printf("thread return ,thread id %x,return code:PTHREAD_CANCELED\n",tid);
55 }
56 else
57 {
58
59 printf("thread return ,thread id %x,return code:NULL\n",tid);
60 }
61
62 return 0;
63 }
线程分离:
默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join,否则无法释放资源,从而造成系统泄露。
当一个线程 被分离后,这个线程无法被pthread_join等待,否则返回EINVAL错误,如图:
线程被分离后,这个线程退出后,自动释放资源,不需要被等待。
5 void *thread_run(void *arg)
6 {
7 pthread_detach(pthread_self());//pthread_self()获取线程自己的ID
8 printf("%s\n",(char *)arg);
9 return NULL;
10 }
11 int main()
12 {
13 pthread_t tid;
14 if(pthread_create(&tid,NULL,thread_run,"thread1 run ...")!=0)
15 {
16 perror("pthread_create"),exit(1);
17 }
18 int ret=0;
19 sleep(1);
20
21 if(pthread_join(tid,NULL)==0)
22 {
23 printf("pthread wait success\n");
24 }
25 else
26 {
27 printf("pthread wait failed\n");
28 ret=1;
29 }
30 return 0;
31
32 }
线程同步与互斥
mutex互斥量:—有时需要线程间共享时,需要对临界资源进行互斥
pthread_mutex_init()初始化互斥锁
pthread_mutex_destroy()删除互斥锁
pthread_mutex_lock():占有互斥锁(阻塞操作)
pthread_mutex_trylock():试图占有互斥锁(不阻塞操作)。即,当互斥锁空闲时,将占有该锁;否则,立即返回。
pthread_mutex_unlock(): 释放互斥锁
条件变量:–当一个线程访问资源时,资源已空,它只能等待,直到有其他线程释放了一个资源
pthread_cond_init():初始化条件变量
pthread_cond_destroy():销毁条件变量
pthread_cond_signal(): 发送一个信号给正在当前条件变量的线程队列中处于阻塞等待状态的线程,使其脱离阻塞状态,唤醒后继续执行。如果没有线程处在阻塞等待状态,pthread_cond_signal也会成功返回。一般只给一个阻塞状态的线程发信号。假如有多个线程正在阻塞等待当前条件变量,则根据各等待线程优先级的高低确定哪个线程接收到信号开始继续执行。如果各线程优先级相同,则根据等待时间的长短来确定哪个线程获得信号。
pthread_cond_wait(): 等待条件变量的特殊条件发生;pthread_cond_wait() 必须与一个pthread_mutex配套使用。该函数调用实际上依次做了3件事:
- 对当前pthread_mutex解锁
- 把当前线程挂起到当前条件变量的线程队列
- 被其它线程的信号唤醒后对当前pthread_mutex申请加锁
如果线程收到一个信号被唤醒,将被配套的互斥锁重新锁住,
pthread_cond_broadcast唤醒全部线程
用互斥量和条件变量实现消费者——生产者模型(只有一个消费者和生产者)
6 int goods=0;
7 pthread_mutex_t mutex;
8 pthread_cond_t cond;
9 void *thr_productor(void *arg)
10 {
11 while(1)
12 {
13 if(goods==0)
14 {
15 printf("product a food\n");//物品为0时,生产
16 goods=1;
17 sleep(1);
18 pthread_cond_signal(&cond);//通知
19 }
20 }
21 return NULL;
22 }
23
24 void *thr_consumer(void *arg)
25 {
26 while(1)
27 {
28 if(goods==0)//没有物品,就等待
29 {
30 pthread_cond_wait(&cond,&mutex);
31 }
32 printf("I got it\n");//代表有物品了
33 goods=0;//得到之后就将它置为0
34 }
35 return NULL;
36
37 }
38 int main()
39 {
40 pthread_t tid1,tid2;
41 int ret;
42 pthread_cond_init(&cond,NULL);
43 pthread_mutex_init(&mutex,NULL);
44 ret=pthread_create(&tid1,NULL,thr_productor,NULL);
45 if(ret!=0)
46 perror("pthread_create"),exit(1);
47
48 ret=pthread_create(&tid1,NULL,thr_consumer,NULL);
49 if(ret!=0)
50 perror("pthread_create"),exit(1);
51 pthread_join(tid1,NULL);
52 pthread_join(tid2,NULL);
53 pthread_cond_destroy(&cond);
54 pthread_mutex_destroy(&mutex);
55 return 0;
56 }
POSIX信号量—-同步操作,达到无冲突的访问共享资源
sem_wait:申请共享资源,所指定信号灯的值如果大于0,那就将它减1并立即返回,就可以使用申请来的共享资源了。如果该值等于0,调用线程就被进入睡眠状态,直到该值变为大于0,这时再将它减1,函数随后返回。sem_wait操作必须是原子操作。
sem_post:释放共享资源。与sem_wait恰相反。
sem_init:初始化非命名(内存)信号量
sem_destroy:摧毁非命名信号量
用POSIX信号量实现生产者–消费者(一对一):
6 sem_t sem_producer;
7 sem_t sem_consumer;
8 void *thr_producer(void *arg)
9 {
10 while(1)
11 {
12 sem_wait(&sem_producer);
13 sleep(1);
14 printf("P:product a food\n");
15 sem_post(&sem_consumer);
16 }
17 }
18 void *thr_consumer(void *arg)
19 {
20 while(1)
21 {
22 sem_wait(&sem_consumer);
23 printf("C:eat a food\n");
24 sem_post(&sem_producer);
25
26 }
27 }
28 int main()
29 {
30 pthread_t tid1,tid2;
31 int ret;
32
33 sem_init(&sem_producer,0,0);
34 sem_init(&sem_consumer,0,1);//初始化消费者线程初始值为1,表示消费者先吃
35 ret=pthread_create(&tid1,NULL,thr_producer,NULL);
36 if(ret!=0)
37 perror("pthread_create"),exit(1);
38
39 ret=pthread_create(&tid2,NULL,thr_consumer,NULL);
40 if(ret!=0)
41 perror("pthread_create"),exit(1);
42 pthread_join(tid1,NULL);
43 pthread_join(tid2,NULL);
44
45 sem_destroy(&sem_producer);
46 sem_destroy(&sem_consumer);
47 }
读写锁:–处理多读少写情况
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t
*restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
nt pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
自旋锁–适合短时间内完成的工作
4 //读写锁的实现
5 char *str="I am a point";
6 pthread_rwlock_t rwlock;
7 void *thr_write(void *arg)
8 {
9 pthread_t p=pthread_self();
10 while(1)
11 {
12 pthread_rwlock_wrlock(&rwlock);
13 printf("W:%p*** writing*** %s\n",p,str);
14 sleep(1);
15 printf("W:****%p***amazing\n",p);
16 pthread_rwlock_unlock(&rwlock);
17 usleep(100);
18 }
19 return NULL;
20 }
21 void *thr_read(void *arg)
22 {
23 pthread_t p=pthread_self();
24 while(1)
25 {
26 pthread_rwlock_rdlock(&rwlock);
27 printf("R:%p*** reading*** %s\n",p,str);
28 sleep(1);
29 printf("R:****%p***amazing\n",p);
30 pthread_rwlock_unlock(&rwlock);
31 usleep(100);
32 }
33 return NULL;
34 }
35
36 int main()
37 {
38 pthread_t tid1[2],tid2[2];
39
40 pthread_rwlock_init(&rwlock,NULL);
41
42 pthread_create(&tid1[0],NULL,thr_write,NULL);
43 pthread_create(&tid1[1],NULL,thr_write,NULL);
44
45 pthread_create(&tid1[0],NULL,thr_read,NULL);
46 pthread_create(&tid1[1],NULL,thr_read,NULL);
47
48 pthread_join(tid1[0],NULL);
49 pthread_join(tid1[1],NULL);
50 pthread_join(tid2[0],NULL);
51 pthread_join(tid2[1],NULL);
52
53 pthread_rwlock_destroy(&rwlock);
54 }
可以看出读 可以同时多个线程读,但写 只能一个线程写完后另一个线程写 ,并且在写的过程中不允许读。