Linux 多线程编程

Stella981
• 阅读 714

1. Linux“线程”

进程与线程之间是有区别的,不过Linux内核只提供了轻量进程的支持,未实现线程模型。Linux是一种“多进程单线程”的操作系统。Linux本身只有进程的概念,而其所谓的“线程”本质上在内核里仍然是进程。
大家知道,进程是资源分配的单位,同一进程中的多个线程共享该进程的资源(如作为共享内存的全局变量)。Linux中所谓的“线程”只是在被创建时clone了父进程的资源,因此clone出来的进程表现为“线程”,这一点一定要弄清楚。因此,Linux“线程”这个概念只有在打冒号的情况下才是最准确的。
目前Linux中最流行的线程机制为LinuxThreads,所采用的就是线程-进程“一对一”模型,调度交给核心,而在用户级实现一个包括信号处理在内的线程管理机制。LinuxThreads由Xavier Leroy (Xavier.Leroy@inria.fr)负责开发完成,并已绑定在GLIBC中发行,它实现了一种BiCapitalized面向Linux的Posix 1003.1c “pthread”标准接口。Linuxthread可以支持Intel、Alpha、MIPS等平台上的多处理器系统。

按照POSIX 1003.1c 标准编写的程序与Linuxthread 库相链接即可支持Linux平台上的多线程,在程序中需包含头文件pthread. h,在编译链接时使用命令:
gcc -D -REENTRANT -lpthread xxx. c

其中-REENTRANT宏使得相关库函数(如stdio.h、errno.h中函数) 是可重入的、线程安全的(thread-safe),-lpthread则意味着链接库目录下的libpthread.a或libpthread.so文件。使用Linuxthread库需要2.0以上版本的Linux内核及相应版本的C库(libc 5.2.18、libc 5.4.12、libc 6)。

2.“线程”控制

线程创建

进程被创建时,系统会为其创建一个主线程,而要在进程中创建新的线程,则可以调用pthread_create

pthread_create(pthread_t *thread, const pthread_attr_t *attr, void* (start_routine)(void*), void *arg**)**;

start_routine为新线程的入口函数,arg为传递给start_routine的参数。

每个线程都有自己的线程ID,以便在进程内区分。线程ID在pthread_create调用时回返给创建线程的调用者;一个线程也可以在创建后使用pthread_self()调用获取自己的线程ID:

pthread_self(void);

线程退出

线程的退出方式有三:

(1) 执行完成后隐式退出;
(2) 由线程本身显示调用pthread_exit 函数退出;
pthread_exit(void * retval) ;

(3) 被其他线程用pthread_cancel函数终止:
pthread_cancel(pthread_t thread) ;

在某线程中调用此函数,可以终止由参数thread 指定的线程。

如果一个线程要等待另一个线程的终止,可以使用pthread_join函数,该函数的作用是调用pthread_join的线程将被挂起直到线程ID为参数thread的线程终止:
pthread_join(pthread_t thread, void** threadreturn**)**;

3.线程通信

线程互斥

互斥意味着“排它”,即两个线程不能同时进入被互斥保护的代码。Linux下可以通过pthread_mutex_t 定义互斥体机制完成多线程的互斥操作,该机制的作用是对某个需要互斥的部分,在进入时先得到互斥体,如果没有得到互斥体,表明互斥部分被其它线程拥有,此时欲获取互斥体的线程阻塞,直到拥有该互斥体的线程完成互斥部分的操作为止。

下面的代码实现了对共享全局变量x 用互斥体mutex 进行保护的目的:

int x; // 进程中的全局变量
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL); //按缺省的属性初始化互斥体变量mutex
pthread_mutex_lock(&mutex); // 给互斥体变量加锁
… //对变量x 的操作
phtread_mutex_unlock(&mutex); // 给互斥体变量解除锁

线程同步

同步就是线程等待某个事件的发生。只有当等待的事件发生线程才继续执行,否则线程挂起并放弃处理器。当多个线程协作时,相互作用的任务必须在一定的条件下同步。

Linux下的C语言编程有多种线程同步机制,最典型的是条件变量(condition variable)。pthread_cond_init用来创建一个条件变量,其函数原型为:
pthread_cond_init (pthread_cond_t *cond, const pthread_condattr_t *attr);

_pthread_cond_wait_和_pthread_cond_timedwait_用来等待条件变量被设置,值得注意的是这两个等待调用需要一个已经上锁的互斥体mutex,这是为了防止在真正进入等待状态之前别的线程有可能设置该条件变量而产生竞争。

pthread_cond_wait的函数原型为:
pthread_cond_wait (pthread_cond_t *cond, pthread_mutex_t *mutex);

pthread_cond_broadcast用于设置条件变量,即使得事件发生,这样等待该事件的线程将不再阻塞:
pthread_cond_broadcast (pthread_cond_t *cond) ;

pthread_cond_signal则用于解除某一个等待线程的阻塞状态:
pthread_cond_signal (pthread_cond_t *cond) ;

pthread_cond_destroy 则用于释放一个条件变量的资源。

在头文件semaphore.h 中定义的信号量则完成了互斥体和条件变量的封装,按照多线程程序设计中访问控制机制,控制对资源的同步访问,提供程序设计人员更方便的调用接口。
sem_init(sem_t *sem, int pshared, unsigned int val);

这个函数初始化一个信号量sem 的值为val,参数pshared 是共享属性控制,表明是否在进程间共享。
sem_wait(sem_t *sem);

调用该函数时,若sem为无状态,调用线程阻塞,等待信号量sem值增加(post )成为有信号状态;若sem为有状态,调用线程顺序执行,但信号量的值减一。
sem_post(sem_t *sem);

调用该函数,信号量sem的值增加,可以从无信号状态变为有信号状态。

4.实例

下面我们还是以名的生产者/消费者问题为例来阐述Linux线程的控制和通信。一组生产者线程与一组消费者线程通过缓冲区发生联系。生产者线程将生产的产品送入缓冲区,消费者线程则从中取出产品。缓冲区有N 个,是一个环形的缓冲池。

  1 #include <stdio.h>
  2 #include <pthread.h>
  3 #define BUFFER_SIZE 16 // 缓冲区数量
  4 struct prodcons
  5 {
  6     // 缓冲区相关数据结构
  7     int buffer[BUFFER_SIZE]; /* 实际数据存放的数组*/
  8     pthread_mutex_t lock; /* 互斥体lock 用于对缓冲区的互斥操作 */
  9     int readpos, writepos; /* 读写指针*/
 10     pthread_cond_t notempty; /* 缓冲区非空的条件变量 */
 11     pthread_cond_t notfull; /* 缓冲区未满的条件变量 */
 12 };
 13 /* 初始化缓冲区结构 */
 14 void init(struct prodcons *b)
 15 {
 16     pthread_mutex_init(&b->lock, NULL);
 17     pthread_cond_init(&b->notempty, NULL);
 18     pthread_cond_init(&b->notfull, NULL);
 19     b->readpos = 0;
 20     b->writepos = 0;
 21 }
 22 /* 将产品放入缓冲区,这里是存入一个整数*/
 23 void put(struct prodcons *b, int data)
 24 {
 25     pthread_mutex_lock(&b->lock);
 26     /* 等待缓冲区未满*/
 27     if ((b->writepos + 1) % BUFFER_SIZE == b->readpos)
 28     {
 29         pthread_cond_wait(&b->notfull, &b->lock);
 30     }
 31     /* 写数据,并移动指针 */
 32     b->buffer[b->writepos] = data;
 33     b->writepos++;
 34     if (b->writepos >= BUFFER_SIZE)
 35         b->writepos = 0;
 36     /* 设置缓冲区非空的条件变量*/
 37     pthread_cond_signal(&b->notempty);
 38     pthread_mutex_unlock(&b->lock);
 39 } 
 40 /* 从缓冲区中取出整数*/
 41 int get(struct prodcons *b)
 42 {
 43     int data;
 44     pthread_mutex_lock(&b->lock);
 45     /* 等待缓冲区非空*/
 46     if (b->writepos == b->readpos)
 47     {
 48         pthread_cond_wait(&b->notempty, &b->lock);
 49     }
 50     /* 读数据,移动读指针*/
 51     data = b->buffer[b->readpos];
 52     b->readpos++;
 53     if (b->readpos >= BUFFER_SIZE)
 54         b->readpos = 0;
 55     /* 设置缓冲区未满的条件变量*/
 56     pthread_cond_signal(&b->notfull);
 57     pthread_mutex_unlock(&b->lock);
 58     return data;
 59 }
 60 
 61 /* 测试:生产者线程将1 到10000 的整数送入缓冲区,消费者线
 62    程从缓冲区中获取整数,两者都打印信息*/
 63 #define OVER ( - 1)
 64 struct prodcons buffer;
 65 void *producer(void *data)
 66 {
 67     int n;
 68     for (n = 0; n < 10000; n++)
 69     {
 70         printf("%d --->\n", n);
 71         put(&buffer, n);
 72     } put(&buffer, OVER);
 73     return NULL;
 74 }
 75 
 76 void *consumer(void *data)
 77 {
 78     int d;
 79     while (1)
 80     {
 81         d = get(&buffer);
 82         if (d == OVER)
 83             break;
 84         printf("--->%d \n", d);
 85     }
 86     return NULL;
 87 }
 88 
 89 int main(void)
 90 {
 91     pthread_t th_a, th_b;
 92     void *retval;
 93     init(&buffer);
 94     /* 创建生产者和消费者线程*/
 95     pthread_create(&th_a, NULL, producer, 0);
 96     pthread_create(&th_b, NULL, consumer, 0);
 97     /* 等待两个线程结束*/
 98     pthread_join(th_a, &retval);
 99     pthread_join(th_b, &retval);
100     return 0;
101 }

5.WIN32、VxWorks、Linux线程类比 

目前为止,笔者已经创作了《基于嵌入式操作系统VxWorks的多任务并发程序设计》(《软件报》2006年5~12期连载)、《深入浅出Win32多线程程序设计》(天极网技术专题)系列,我们来找出这两个系列文章与本文的共通点。 

看待技术问题要瞄准其本质,不管是Linux、VxWorks还是WIN32,其涉及到多线程的部分都是那些内容,无非就是线程控制和线程通信,它们的许多函数只是名称不同,其实质含义是等价的,下面我们来列个三大操作系统共同点详细表单: 

事项

WIN32

VxWorks

Linux

线程创建

CreateThread

taskSpawn

pthread_create

线程终止

执行完成后退出;线程自身调用ExitThread函数即终止自己;被其他线程调用函数TerminateThread函数

执行完成后退出;由线程本身调用exit退出;被其他线程调用函数taskDelete终止

执行完成后退出;由线程本身调用pthread_exit 退出;被其他线程调用函数pthread_cance终止

获取线程ID

GetCurrentThreadId

taskIdSelf

pthread_self

创建互斥

CreateMutex

semMCreate

pthread_mutex_init

获取互斥

WaitForSingleObject、WaitForMultipleObjects

semTake

pthread_mutex_lock

释放互斥

ReleaseMutex

semGive

phtread_mutex_unlock

创建信号量

CreateSemaphore

semBCreate、semCCreate

sem_init

等待信号量

WaitForSingleObject

semTake

sem_wait

释放信号量

ReleaseSemaphore

semGive

sem_post

6.小结

本章讲述了Linux下多线程的控制及线程间通信编程方法,给出了一个生产者/消费者的实例,并将Linux的多线程与WIN32、VxWorks多线程进行了类比,总结了一般规律。鉴于多线程编程已成为开发并发应用程序的主流方法,学好本章的意义也便不言自明。

 1 #include <stdio.h>                                                              
 2 #include <stdio.h>
 3 #include <pthread.h>
 4 void thread(void)                                                               
 5 {                                                                               
 6     int i;                                                                      
 7     for(i=0;i<3;i++)                                                            
 8         printf("This is a pthread.\n");                                         
 9 }
10  
11 int main(void)                                                                  
12 {                                                                               
13     pthread_t id;                                                               
14     int i,ret;                                                                  
15     ret=pthread_create(&id,NULL,(void *) thread,NULL);                          
16     if(ret!=0){                                                                 
17         printf ("Create pthread error!\n");                                     
18         exit (1);                                                               
19     }                                                                           
20     for(i=0;i<3;i++)                                                            
21         printf("This is the main process.\n");                                  
22     pthread_join(id,NULL);                                                      
23     return (0);                                                                 
24 }

编译:gcc example1.c -lpthread -o example1

 1 #include <pthread.h>
 2 #include <stdio.h>
 3 #include <sys/time.h>
 4 #include <string.h>
 5 #define MAX 10
 6  
 7 pthread_t thread[2];
 8 pthread_mutex_t mut;
 9 int number=0, i;
10  
11 void *thread1()
12 {
13     printf ("thread1 : I'm thread 1\n");
14     for (i = 0; i < MAX; i++)
15         {
16             printf("thread1 : number = %d\n",number);
17             pthread_mutex_lock(&mut);
18             number++;
19             pthread_mutex_unlock(&mut);
20             sleep(2);
21         }
22     printf("thread1 :主函数在等我完成任务吗?\n");
23  
24     pthread_exit(NULL);
25  
26 }
27  
28 void *thread2()
29 {
30     printf("thread2 : I'm thread 2\n");
31     for (i = 0; i < MAX; i++)
32         {
33             printf("thread2 : number = %d\n",number);
34             pthread_mutex_lock(&mut);
35             number++;
36             pthread_mutex_unlock(&mut);
37             sleep(3);
38         }
39     printf("thread2 :主函数在等我完成任务吗?\n");
40     pthread_exit(NULL);
41 }
42  
43  
44 void thread_create(void)
45 {
46     int temp;
47     memset(&thread, 0, sizeof(thread)); //comment1
48     //创建线程
49     if((temp = pthread_create(&thread[0], NULL, thread1, NULL)) != 0) //comment2
50         printf("线程1创建失败!\n");
51     else
52         printf("线程1被创建\n");
53     if((temp = pthread_create(&thread[1], NULL, thread2, NULL)) != 0) //comment3
54         printf("线程2创建失败");
55     else
56         printf("线程2被创建\n");
57 }
58  
59 void thread_wait(void)
60 {
61     //等待线程结束
62     if(thread[0] !=0) { //comment4
63         pthread_join(thread[0],NULL);
64         printf("线程1已经结束\n");
65     }
66     if(thread[1] !=0) { //comment5
67         pthread_join(thread[1],NULL);
68         printf("线程2已经结束\n");
69     }
70 }
71  
72 int main()
73 {
74     //用默认属性初始化互斥锁
75     pthread_mutex_init(&mut,NULL);
76     printf("我是主函数哦,我正在创建线程,呵呵\n");
77     thread_create();
78     printf("我是主函数哦,我正在等待线程完成任务阿,呵呵\n");
79     thread_wait();
80     return 0;
81 }

编译 :  gcc -lpthread -o thread_example lp.c

http://www.cnblogs.com/BiffoLee/archive/2011/11/18/2254540.html

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
4、jstack查看线程栈信息
1、介绍利用jps、top、jstack命令找到进程中耗时最大的线程,以及线程状态等等,同时最后还可以显示出死锁的线程查找:FoundoneJavaleveldeadlock即可1、jps获得进程号!(https://oscimg.oschina.net/oscnet/da00a309fa6
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这