1.概念:
线程是参与系统调度的最小单位,它被包含在进程中,是进程的实际运行单位
一个进程可以创建多个线程,多个线程并发运行,每个线程执行不同的任务
2.如何创建线程
当一个程序启动的时候,一个进程被os创建,同时一个线程也立刻开始运行,这个线程被叫做主线程,main()函数是主线程的入口函数
任何一个进程都包含了一个主线程,只有主线程的进程被称为单线程进程
多线程:除了主线程以外,还包含其他的线程,其他线程通常是由主线程创建来的
主线程的作用:
1.创建其他的子线程
2.执行清理工作
3.线程的特点:
线程是程序执行的最基本的单位,进程不能运行,真正运行的是进程中的线程,同一个进程的多个线程共享这个线程的全部系统资源,但同意进程的多个线程有各自的调用栈,自己的寄存器环境,自己的线程本地存储
1.线程不单独存在,被包含在进程里面
2.线程是参与系统调度的基本单位
3.可并发执行,同一个进程的多个线程可以同时执行【宏观】
4.共享进程资源,所以线程拥有相同的地址空间【进程的地址空间】这意味着,线程可以访问该地址空间的每一个虚地址,此外,还可以访问进程所拥有的已打开文件、定时器、信号量等等
4.线程与进程
进程创建多个子进程可以实现并发多任务【本质上是多线程进程】,多线程同样也可以实现并发处理多任务的需求
多进程编程:
进程间切换开销大,多个进程同时运行,微观上是轮流切换运行,进程间切换开销远大于同一进程的多个线程切换的开销
进程间的通信比较麻烦,同一进程的多个线程通信非常方便
线程创建的速度远大于进程创建的速度
5.并发和并行
并发:交替做不同的事情【相比于串行,不再等待上一个任务完成之后再做下一个任务】
并行:同时做不同的事情
6.线程ID
线程和进程一样,都有属于自己的ID,
pthread_t pthread_self(void);
创建ID,返回当前线程的线程ID
int pthread_equal(pthread_t t1, pthread_t t2);
判断两个线程ID是否相等,相等返回非零, 不相等返回0
7.创建线程
在创建第一个进程的时候,也会自动生成一个线程,也就是主线程,主线程可以使用库函数 pthread_create()负责创建一个新的线程,创建出来的新线程被称为主线程的子线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
static void *new_thread_start(void *arg)
{
printf("新线程: 进程 ID<%d> 线程 ID<%lu>\n", getpid(), pthread_self());
return (void *)0;
}
int main(void)
{
pthread_t tid;
int ret;
ret = pthread_create(&tid, NULL, new_thread_start, NULL);
if (ret) {
fprintf(stderr, "Error: %s\n", strerror(ret));
exit(-1);
}
printf("主线程: 进程 ID<%d> 线程 ID<%lu>\n", getpid(), pthread_self());
sleep(1);
exit(0);
}
运行结果:
8.终止线程:
void pthread_exit(void *retval);
该返回值可以通过另一个线程调用pthread_join()来获取
如果在start函数中return也可以通过pthread_join()来获取
pthread_exit()和在线程start函数中和return的作用是一样的,但是区别在于可以在任意位置终止线程
int pthread_join(pthread_t thread, void **retval);
pthread_t thread:指定等待的线程ID
retval:如果不为NULL,则返回的是start函数的return的返回值或者通过pthread_exit()的指定返回值,将返回值复制到retval所指向的内存空间
成功返回0,失败返回错误码
调用pthread_join()会等待指定的线程结束后回收线程,但多个线程同时尝试调用pthread_join()等待指定线程的终止,那结果将是不确定的
如果线程终止了,但是却没有pthread_join()函数去回收线程,则该线程变为了僵尸线程,会导致系统资源的浪费,如果积累太多,则程序无法创建新的线程
和wait()的进程相比的区别:
1.线程之间的关系是对等。每个进程的任意线程均可以调用pthread_join()来等待另一个线程的结束。和父子进程不同的是,父进程通过fork()创建了子进程,也只可以wait()这个子进程,不可以等待其他的。
2.不可以用非阻塞的方式调用pthread_join()
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
static void *new_thread_start(void *arg)
{
printf("新线程 start\n");
sleep(2);
printf("新线程 end\n");
pthread_exit((void *)10);
}
int main(void)
{
pthread_t tid;
void *tret;
int ret;
ret = pthread_create(&tid, NULL, new_thread_start, NULL);
if (ret) {
fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
exit(-1);
}
ret = pthread_join(tid, &tret);
if (ret) {
fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
exit(-1);
}
printf("新线程终止, code=%ld\n", (long)tret);
exit(0);
}
运行结果:
9.取消线程
int pthread_cancel(pthread_t thread);
取消状态以及类型
int pthread_setcancelstate(int state, int *oldstate);
⚫ PTHREAD_CANCEL_ENABLE:线程可以取消,这是新创建的线程取消性状态的默认值,所以
新建线程以及主线程默认都是可以取消的。
⚫ PTHREAD_CANCEL_DISABLE:线程不可被取消,如果此类线程接收到取消请求,则会将请求
挂起,直至线程的取消性状态变为 PTHREAD_CANCEL_ENABLE
eg. pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
当发送一个终止线程的请求时,线程时不会终止的,因为这个是“线程不可以被取消”
int pthread_setcanceltype(int type, int *oldtype);
⚫ PTHREAD_CANCEL_DEFERRED:取消请求到来时,线程还是继续运行,取消请求被挂起,直
到线程到达某个取消点为止,这是所有新建线程包括主线程默认的取消性类型
⚫ PTHREAD_CANCEL_ASYNCHRONOUS:可能会在任何时间点(也许是立即取消,但不一定)
取消线程
取消点:
取消点其实就是一系列函数,当执行到这些函数的时候,才会真正响应取消请求,这些函数就是取消点比如在执行到for,while永久循环的时候,线程就不会被请求终止
void pthread_testcancel(void);
eg.
static void *new_thread_start(void *arg)
{
printf("新线程--start run\n");
for ( ; ; ) {
pthread_testcancel();
}
return (void *)0;
}
10.分离线程
概念:也就是当线程结束的时候,希望线程终止的时候自动回收线程资源并且将其移除
int pthread_detach(pthread_t thread);
线程的分离过程是不可逆的,处于分离状态的线程,当其终止后,则会自动的回收线程资源
一旦分离,就可以通过pthread_join()来获取其终止状态
pthread_detach(pthread_self()); //分离自己
11.线程清理函数
void pthread_cleanup_push(void (*routine)(void *), void *arg);
将线程加入到清理函数栈,方法和栈一样,先入后出
void pthread_cleanup_pop(int execute);
当execute为0的时候,只会去清理函数栈中的最顶层的函数移除,如果非0,可以手动清除函数
12.线程属性
创建的每一个线程都可以配置线程栈的大小以及分配空间
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize);
int pthread_attr_setstackaddr(pthread_attr_t *attr, void *stackaddr);
int pthread_attr_getstackaddr(const pthread_attr_t *attr, void **stackaddr);
eg.
/* 对 attr 对象进行初始化 */
pthread_attr_init(&attr);
/* 设置栈大小为 4K */
pthread_attr_setstacksize(&attr, 4096);
....
....
/* 销毁 attr 对象 */
pthread_attr_destroy(&attr);
进程中创建的每一个线程都有自己的栈地址空间,被称为线程栈。也就是代表了每个线程都有属于自己的局部变量
可重入函数:
一个函数被同一个进程的多个不同的执行流同时调用【宏观】
线程同步
作用:为了对共享资源的访问进行保护,解决数据一致性的问题,防止多个线程对共享资源的并发访问,所以要进行保护
互斥锁
当访问共享资源之前对对互斥锁进行上锁,在访问资源完成后,然后释放互斥锁(解锁),上锁后,任何其他试图再次对互斥锁已经加锁线程都会被阻塞,直到线程释放互斥锁。以此类推,别的企图继续加锁的设备必须要阻塞等待,上一个线程对共享资源解锁后,才可以进行加锁
互斥锁初始化:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
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);
对于锁定状态的互斥锁进行解锁
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
static pthread_mutex_t mutex;
static int g_count = 0;
static void *new_thread_start(void *arg)
{
int loops = *((int *)arg);
int l_count, j;
for (j = 0; j < loops; j++) {
pthread_mutex_lock(&mutex); //互斥锁上锁
l_count = g_count;
l_count++;
g_count = l_count;
pthread_mutex_unlock(&mutex);//互斥锁解锁
}
return (void *)0;
}
static int loops;
int main(int argc, char *argv[])
{
pthread_t tid1, tid2;
int ret;
/* 获取用户传递的参数 */
if (2 > argc)
loops = 10000000; //没有传递参数默认为 1000 万次
else
loops = atoi(argv[1]);
/* 初始化互斥锁 */
pthread_mutex_init(&mutex, NULL);
/* 创建 2 个新线程 */
ret = pthread_create(&tid1, NULL, new_thread_start, &loops);
if (ret) {
fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
exit(-1);
}
ret = pthread_create(&tid2, NULL, new_thread_start, &loops);
if (ret) {
fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
exit(-1);
}
/* 等待线程结束 */
ret = pthread_join(tid1, NULL);
if (ret) {
fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
exit(-1);
}
ret = pthread_join(tid2, NULL);
if (ret) {
fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
exit(-1);
}
/* 打印结果 */
printf("g_count = %d\n", g_count);
exit(0);
}
运行结果:V
pthread_mutex_trylock()函数
当互斥锁已经被其他线程锁住的时候,调用该函数,不会导致线程阻塞,而是直接返回错误码。
int pthread_mutex_trylock(pthread_mutex_t *mutex);
while(pthread_mutex_trylock(&mutex)); //以非阻塞方式上锁
{
l_count = g_count;
l_count++;
g_count = l_count;
pthread_mutex_unlock(&mutex);//互斥锁解锁
}
销毁互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
被销毁的互斥锁则不可以进行上锁和解锁,需要再次初始化才可以
互斥锁死锁
当一个线程A一直锁住资源A,但想同时锁住线程B的资源B【线程A处于阻塞态】,线程B又想锁住线程A锁住的共享资源,这会导致两个线程都处于阻塞态,也就是产生了死锁
条件变量:
生产者和消费者模型:#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> #include <string.h> static pthread_mutex_t mutex; static int g_avail = 0; /* 消费者线程 */ static void *consumer_thread(void *arg) { for ( ; ; ) { pthread_mutex_lock(&mutex);//上锁 while (g_avail > 0) g_avail--; //消费 pthread_mutex_unlock(&mutex);//解锁 } return (void *)0; } /* 主线程(生产者) */ int main(int argc, char *argv[]) { pthread_t tid; int ret; /* 初始化互斥锁 */ pthread_mutex_init(&mutex, NULL); /* 创建新线程 */ ret = pthread_create(&tid, NULL, consumer_thread, NULL); if (ret) { fprintf(stderr, "pthread_create error: %s\n", strerror(ret)); exit(-1); } for ( ; ; ) { pthread_mutex_lock(&mutex);//上锁 g_avail++; //生产 pthread_mutex_unlock(&mutex);//解锁 } exit(0); }
主线程生产者一直生产,新线程也一直在循环判断,会导致CPU资源的浪费当采用条件变量,当条件未到达,线程处于休眠状态,当满足条件,线程会被唤醒
条件变量初始化
1.宏初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
2.函数初始化
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
attr指针:指向一个 pthread_condattr_t 类型对象,pthread_condattr_t 数据类型用于描述条件变量的属性【和互斥锁一样】
销毁条件变量:
int pthread_cond_destroy(pthread_cond_t *cond);
通知和等待条件变量:
发送信号给一个线程或者多个线程,通知某个共享变量的状态发生了改变
int pthread_cond_broadcast(pthread_cond_t *cond);
可以唤醒所有等待的线程
int pthread_cond_signal(pthread_cond_t *cond);
唤醒至少一个线程,更高效
等待:在收到一个通知之前一直处于阻塞状态
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
cond:条件
mutex:互斥锁的对象
条件变量的判断条件【必须while循环】
⚫ 当有多于一个线程在等待条件变量时,任何线程都有可能会率先醒来获取互斥锁,率先醒来获取到 互斥锁的线程可能会对共享变量进行修改,进而改变判断条件的状态。譬如示例代码 12.3.2 中, 如果有两个或更多个消费者线程,当其中一个消费者线程从 pthread_cond_wait() 返回后,它会将全局共享变量 g_avail 的值变成 0 ,导致判断条件的状态由真变成假⚫ 可能会发出虚假的通知
自旋锁
和互斥锁的区别:
互斥锁在无法获取到锁时会让线程陷入阻塞等待状态;而自旋锁在无法获取到锁时,将会在原地“自旋”等待缺点:当未获得锁的时候,会一直占用CPU的资源。试图对同一自旋锁两次必然会导致死锁,但是互斥锁却不会使用场景:自旋锁在用户态应用程序中使用的比较少,通常在内核代码中使用比较多;因为自旋锁可以在中断服务函数中使用,而互斥锁则不行,在执行中断服务函数时要求不能休眠、不能 被抢占(内核中使用自旋锁会自动禁止抢占),一旦休眠意味着执行中断服务函数时主动交出了 CPU 使用权,休眠结束时无法返回到中断服务函数中,这样就会导致死锁
自旋锁初始化:
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
pshared:
⚫ PTHREAD_PROCESS_SHARED:共享自旋锁。该自旋锁可以在多个进程中的线程之间共享
⚫ PTHREAD_PROCESS_PRIVATE:私有自旋锁。只有本进程内的线程才能够使用该自旋锁
销毁自旋锁:
int pthread_spin_destroy(pthread_spinlock_t *lock);
自旋锁加锁和解锁
int pthread_spin_lock(pthread_spinlock_t *lock);
int pthread_spin_trylock(pthread_spinlock_t *lock); 非阻塞加锁
int pthread_spin_unlock(pthread_spinlock_t *lock);
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
static pthread_spinlock_t spin;//定义自旋锁
static int g_count = 0;
static void *new_thread_start(void *arg)
{
int loops = *((int *)arg);
int l_count, j;
for (j = 0; j < loops; j++) {
pthread_spin_lock(&spin); //自旋锁上锁
l_count = g_count;
l_count++;
g_count = l_count;
pthread_spin_unlock(&spin);//自旋锁解锁
}
return (void *)0;
}
static int loops;
int main(int argc, char *argv[])
{
pthread_t tid1, tid2;
int ret;
/* 获取用户传递的参数 */
if (2 > argc)
loops = 10000000; //没有传递参数默认为 1000 万次
else
loops = atoi(argv[1]);
/* 初始化自旋锁(私有) */
pthread_spin_init(&spin, PTHREAD_PROCESS_PRIVATE);
/* 创建 2 个新线程 */
ret = pthread_create(&tid1, NULL, new_thread_start, &loops);
if (ret) {
fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
exit(-1);
}
ret = pthread_create(&tid2, NULL, new_thread_start, &loops);
if (ret) {
fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
exit(-1);
}
/* 等待线程结束 */
ret = pthread_join(tid1, NULL);
if (ret) {
fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
exit(-1);
}
ret = pthread_join(tid2, NULL);
if (ret) {
fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
exit(-1);
}
/* 打印结果 */
printf("g_count = %d\n", g_count);
/* 销毁自旋锁 */
pthread_spin_destroy(&spin);
exit(0);
}
运行结果:
自旋锁会比互斥锁快一点
读写锁
三种状态:
1.读模式下的加锁状态(读加锁状态)2.写模式下的加锁状态(写加锁状态)
3.不加锁
规则:
⚫ 当读写锁处于写加锁状态时,在这个锁被解锁之前,所有试图对这个锁进行加锁操作(不管是以读模式加锁还是以写模式加锁)的线程都会被阻塞⚫ 当读写锁处于读加锁状态时,所有试图以读模式对它进行加锁的线程都可以加锁成功;但是任何以写模式对它进行加锁的线程都会被阻塞,直到所有持有读模式锁的线程释放它们的锁为止
读写锁初始化
1.宏初始化
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
2.函数初始化
int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr);
销毁读写锁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
读写锁上锁和解锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
写加锁模式:其他线程调用会失败
读加锁模式:其他线程调用会成功
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
写加锁模式:其他线程调用会失败
读加锁模式:其他线程调用会失败
非阻塞加锁
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
解锁
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
static pthread_rwlock_t rwlock;//定义读写锁
static int g_count = 0;
static void *read_thread(void *arg)
{
int number = *((int *)arg);
int j;
for (j = 0; j < 10; j++) {
pthread_rwlock_rdlock(&rwlock); //以读模式获取锁
printf("读线程<%d>, g_count=%d\n", number+1, g_count);
pthread_rwlock_unlock(&rwlock);//解锁
sleep(1);
}
return (void *)0;
}
static void *write_thread(void *arg)
{
int number = *((int *)arg);
int j;
for (j = 0; j < 10; j++) {
pthread_rwlock_wrlock(&rwlock); //以写模式获取锁
printf("写线程<%d>, g_count=%d\n", number+1, g_count+=20);
pthread_rwlock_unlock(&rwlock);//解锁
sleep(1);
}
return (void *)0;
}
static int nums[5] = {0, 1, 2, 3, 4};
int main(int argc, char *argv[])
{
pthread_t tid[10];
int j;
/* 对读写锁进行初始化 */
pthread_rwlock_init(&rwlock, NULL);
/* 创建 5 个读 g_count 变量的线程 */
for (j = 0; j < 5; j++)
pthread_create(&tid[j], NULL, read_thread, &nums[j]);
/* 创建 5 个写 g_count 变量的线程 */
for (j = 0; j < 5; j++)
pthread_create(&tid[j+5], NULL, write_thread, &nums[j]);
/* 等待线程结束 */
for (j = 0; j < 10; j++)
pthread_join(tid[j], NULL);//回收线程
/* 销毁自旋锁 */
pthread_rwlock_destroy(&rwlock);
exit(0);
}
运行结果:
读写锁的属性
int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *attr, int *pshared);
获取读写锁的共享属性
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared);
设置读写锁的共享属性
pshared:
⚫ PTHREAD_PROCESS_SHARED:共享读写锁。该读写锁可以在多个进程中的线程之间共享;
⚫ PTHREAD_PROCESS_PRIVATE:私有读写锁。只有本进程内的线程才能够使用该读写锁,这是