Linux下线程编程(2)

时间:2023-02-09 11:20:32

1.线程简介

  线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。

  线程是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针PC,寄存器集合和堆栈组成。线程是进程的实体,是被系统独立调度和分配的基本单位。一个线程可以创建和撤销另一个线程,同一进程的多个线程之间可以并发执行。线程由就绪、阻塞、运行三种基本状态。每一个程序至少有一个线程,若程序只有一个线程,那就是程序本身。

  在同一进程中的各个线程,都可以共享该进程所拥有的资源,这首先表现在:所有线程都具有相同的地址空间(进程的地址空间),这意味着,线程可以访问该地址空间的每一个虚地址;此外,还可以访问进程所拥有的已打开文件、定时器、信号量机构等。由于同一个进程内的线程共享内存和文件,所以线程之间互相通信不必调用内核。

Linux下线程编程(2)

2.线程相关接口函数

 2.1 线程取消函数pthread_cancel

​int pthread_cancel(pthread_t thread);

函数功能:

  取消同一进程中的其他线程。

形 参:

  pthread_t thread — 线程描述符

返回值: 0 — 成功,其他值 — 失败​

  示例:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *start_routine_func(void *arg)
{
int data=*(int *)arg;
while(1)
{
printf("data=%d\n",data);
sleep(1);
data++;
}
}
int main()
{
int data=10;
pthread_t pth_id;
if(pthread_create(&pth_id,NULL,start_routine_func,&data)!=0)
{
printf("线程创建失败\n");
return 0;
}
printf("子线程ID:%lu\n",pth_id);
while(1)
{
sleep(1);
printf("主线程运行中data=%d\n",data);
data++;
if(data==15)
{
pthread_cancel(pth_id);//取消子线程
}
}
}
[xsw@xsw 系统编程]$ gcc pthread.c -lpthread
[xsw@xsw 系统编程]$ ./a.out
子线程ID:3079162736
data=10
主线程运行中data=10
data=11
主线程运行中data=11
data=12
主线程运行中data=12
data=13
主线程运行中data=13
data=14
主线程运行中data=14
主线程运行中data=15
主线程运行中data=16
主线程运行中data=17

 2.2 线程分离属性pthread_detach

     创建一个线程默认的状态是joinable(结合属性),如果一个线程结束但没有调用pthread_join,则它的状态类似于进程中的zombie process(僵尸进程),即还有一部分资源没有被回收(退出状态码),所以创建线程时应该使用函数pthread_join来等待线程运行结束,并可得到线程的退出代码,回收其资源(类似进程中的wait、waitpid)。但是调用pthread_join(pthread_id)函数后,如果该线程没有运行结束,调用者会被阻塞,有些情况下我们并不希望如此。pthread_detach函数可以将该线程状态设置为detached(分离状态),则该线程运行结束后自动会释放所有资源。

  函数原型:

​​int pthread_detach(pthread_t thread);

形 参:

  pthread_t thread — 线程标志符

返回值: 0 — 成功,其它值 – 失败​

  示例:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *start_routine_func(void *arg)
{
int data=*(int *)arg;
while(1)
{
printf("data=%d\n",data);
sleep(1);
data++;
}
}
int main()
{
int data=10;
pthread_t pth_id;
if(pthread_create(&pth_id,NULL,start_routine_func,&data)!=0)
{
printf("线程创建失败\n");
return 0;
}
printf("子线程ID:%lu\n",pth_id);
//设置分离属性
pthread_detach(pth_id);
//等待子线程退出
pthread_join(pth_id,NULL);//未设置分离属性则会阻塞主线程
while(1)
{
sleep(1);
printf("主线程运行中...\n");
}
return 0;
}
[xsw@xsw 系统编程]$ gcc pthread.c -lpthread
[xsw@xsw 系统编程]$ ./a.out
子线程ID:3078335344
data=10

主线程运行中...
data=11
主线程运行中...
data=12
主线程运行中...
data=13
主线程运行中...
data=14
主线程运行中...
data=15

2.3 设置线程栈空间

  • 查看线程堆栈空间:
[wbyq@wbyq ~]$ ulimit -s
8192

  8192单位是KB,也就是默认栈空间大小为8M

  • 通过命令ulimit -a查看线程栈空间详细信息

Linux下线程编程(2)

  • 通过命令ulimit -s <栈空间大小>

Linux下线程编程(2)

​  每个线程的栈空间都是独立的,如果堆栈空间溢出程序会出现段错误。如果一个进程有10个线程,那么分配的栈空间大小为10*<每个线程栈空间大小>

  示例:

#include <stdio.h>
int main()
{
char buff[12*1024*1024+1]="hello,world\n";
printf("buff=%s,%d",buff,sizeof(buff));
return 0;
}
[xsw@xsw 系统编程]$ ./a.out
段错误 (core dumped)

2.4 通过函数设置和查询线程栈空间

#include <stdio.h>
#include <pthread.h>
#include <limits.h>
int main()
{
/*查看线程栈空间最小值*/
printf("STACK_MIN:%d\n",PTHREAD_STACK_MIN);//16384byte--16kb
pthread_attr_t attr;
size_t ret,stack_size;
ret=pthread_attr_init(&attr);//初始化线程属性
if(ret!=0)
{
printf("初始化失败\n");
return 0;
}
/*获取线程栈空间*/
ret=pthread_attr_getstacksize(&attr,&stack_size);
printf("线程栈空间:%ld kb\n",stack_size/1024);
/*设置线程栈空间*/
stack_size=8*1024*1024;//8M
pthread_attr_setstacksize(&attr,stack_size);
/*获取线程栈空间*/
ret=pthread_attr_getstacksize(&attr,&stack_size);
printf("修改后栈空间:%ld kb\n",stack_size/1024);
}
[wbyq@wbyq ubuntu]$ gcc main.c -pthread
[wbyq@wbyq ubuntu]$ ./a.out
STACK_MIN:16384
线程栈空间:10240 kb
修改后栈空间:8192 kb

3.线程间通讯

      线程间常用通讯方式有以下几种:

  1. 互斥锁(普通锁、检错锁、嵌套锁);
  2.  读写锁
  3. 自旋锁
  4. 条件变量
  5. 围栏机制
  6. 信号量

3.1 互斥锁简介

  在编程中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为" 互斥锁" 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。

    互斥锁(Mutex)是在原子操作API的基础上实现的信号量行为。互斥锁不能进行递归锁定或解锁,能用于交互上下文但是不能用于中断上下文,同一时间只能有一个任务持有互斥锁,而且只有这个任务可以对互斥锁进行解锁。

    互斥锁是一种简单的加锁的方法来控制对共享资源的存取,当多个线程访问公共资源时,为了保证同一时刻只有一个线程独占资源,就可以通过互斥锁加以限制,在一个时刻只能有一个线程掌握某个互斥锁,拥有上锁状态的线程才能够对共享资源进行操作。若其他线程希望上锁一个已经上锁了的互斥锁,则该线程就会挂起,直到上锁的线程释放掉互斥锁为止。

3.2 互斥锁相关函数

     在Posix Thread中定义有一套专门用于线程同步的mutex函数。可以通过静态和动态两种方式创建互斥锁。

 互斥锁有三个类型可供选择:

 PTHREAD_MUTEX_TIMED_NP普通锁(默认锁):

    当一个线程加锁以后,其余请求锁的线程将形成一个阻塞等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。

 PTHREAD_MUTEX_RECURSIVE_NP嵌套锁:

    允许同一个线程对同一个锁成功获得多次,并通过多次unlock 解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。

    嵌套锁对同一线程可以重复上锁成功,对不同线程不能重复上锁。

    嵌套锁在同一线程中重复上锁,需要重复解锁,否则其它线程将阻塞。

 PTHREAD_MUTEX_ERRORCHECK_NP检错锁:

    如果同一个线程请求同一个锁,则返回 EDEADLK,否则与普通锁类型动作相同。 这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。

    检错锁的主要特点就是: 同一个线程无法多次重复进行加锁, 第一次获取锁成功后, 没有解锁的情况下, 如果继续获取锁将不会阻塞, 会返回一个错误值(35)。

动态方式初始化互斥锁:

int pthread_mutex_init(pthread_mutex_t *restrict mutex,constpthread_mutexattr_t *restrict attr);

 attr填NULL表示使用默认属性,创建普通锁。

//静态方式初始化互斥锁

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

//互斥锁上锁,多次请求则会阻塞

int pthread_mutex_lock(pthread_mutex_t *mutex);

//互斥锁上锁,多次请求不会阻塞,会返回上锁失败错误信息

int pthread_mutex_trylock(pthread_mutex_t *mutex);

//互斥解锁

int pthread_mutex_unlock(pthread_mutex_t *mutex);

//销毁互斥锁

int pthread_mutex_destroy(pthread_mutex_t *mutex);


3.3 互斥锁编程

  • 练习1

  1.创建1个线程,子线程先打印10遍hello,world,然后主线程再打印5遍”12346”,按次顺序循环50次。

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
/*
1.创建1个线程,子线程先打印10遍hello,world,然后主线程再打印5遍”12346”,按次顺序循环50次。
*/
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//静态初始化互斥锁1
pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;//静态初始化互斥锁2
void *pth_work(void *arg)
{
int i,j;
for(i=0;i<50;i++)
{
pthread_mutex_lock(&mutex);
printf("-----------子线程第%d遍-----------\n",i);
for(j=0;j<10;j++)
{
printf("hello,world\n");
}
pthread_mutex_unlock(&mutex2);
}
}
int main()
{
int i=0,j;
pthread_t pthid;
pthread_mutex_lock(&mutex2);
/*创建子线程*/
pthread_create(&pthid,NULL,pth_work,NULL);//创建线程
pthread_detach(pthid);
for(i=0;i<50;i++)
{
pthread_mutex_lock(&mutex2);//互斥锁上锁
printf("-----------主线程第%d遍-----------\n",i);
for(j=0;j<5;j++)//主线程打印
{
printf("123456\n");
}
pthread_mutex_unlock(&mutex);
}
pthread_mutex_destroy(&mutex);
pthread_mutex_destroy(&mutex2);

}

Linux下线程编程(2)

  • 练习2

  2.创建3线程,线程1打印A,线程2打印B,线程3打印C,按照ABC顺序输出10遍。

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
/*
2.创建3线程,线程1打印A,线程2打印B,线程3打印C,按照ABC顺序输出10遍。
*/
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//静态初始化互斥锁1
pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;//静态初始化互斥锁2
pthread_mutex_t mutex3 = PTHREAD_MUTEX_INITIALIZER;//静态初始化互斥锁3
void *pth_work(void *arg)
{
int cnt=(int *)arg;
//printf("cnt=%d\n",cnt);
for(int i=0;i<10;i++)
{
if(cnt==0)//线程1
{
pthread_mutex_lock(&mutex);
printf("A");
pthread_mutex_unlock(&mutex2);
}
if(cnt==1)//线程2
{
pthread_mutex_lock(&mutex2);
printf("B");
pthread_mutex_unlock(&mutex3);
}
if(cnt==2)//线程3
{
pthread_mutex_lock(&mutex3);
printf("C");
fflush(stdout);//刷新缓冲区
pthread_mutex_unlock(&mutex);
}
}
}
int main()
{
int i=0;
pthread_t pthid[3];
pthread_mutex_lock(&mutex2);
pthread_mutex_lock(&mutex3);
/*创建3个子线程*/
for(i=0;i<3;i++)
{
pthread_create(&pthid[i],NULL,pth_work,(void *)i);
}
/*等待线程结束*/
for(i=0;i<3;i++)
{
pthread_join(pthid[i],NULL);
}
printf("\n所有子线程结束,程序退出!\n");
pthread_mutex_destroy(&mutex);
pthread_mutex_destroy(&mutex2);
pthread_mutex_destroy(&mutex3);
}
  • 运行效果:

[wbyq@wbyq ubuntu]$ ./app  

ABCABCABCABCABCABCABCABCABCABC

所有子线程结束,程序退出!