Linux系统编程 - 线程thread

张开发
2026/5/17 9:27:59 15 分钟阅读
Linux系统编程 - 线程thread
目录一、线程介绍1.概念2.线程和进程的区别1共同点2不同点二、线程编程步骤1.线程创建2.获取线程ID3.线程终止4.线程的回收示例代码三、线程参数传递与返回值1.参数传递方式2.可传递的数据类型3.线程返回值示例代码四、线程同步与互斥重要1.互斥锁 (Mutex)概念提供对临界资源排他性访问的机制一次只允许一个线程进入临界区。编程步骤定义锁pthread_mutex_t mutex;初始化锁pthread_mutex_init(mutex, NULL);加锁pthread_mutex_lock(mutex); // 进入临界区前解锁pthread_mutex_unlock(mutex); // 离开临界区后销毁锁pthread_mutex_destroy(mutex);示例代码2.信号量 (Semaphore)计数信号量信号量的第二种用法3.死锁定义产生必要条件四个必须同时满足一、线程介绍1.概念线程是轻量级的进程通常指一个进程内部的多个执行任务不同的控制流/不同线程。2.线程和进程的区别1共同点并发。2不同点线程数据共享 对应的栈区是私有 进程用户空间的内存都是私用的。线程(小)属于某一个进程(大) 从属关系。进程创建的开销大(0~3G)线程只需要开辟对应的栈(8M)空间其他区域共享进程的进程适合需要大量资源的复杂任务 不需要大的资源的任务线程完成二、线程编程步骤核心步骤创建线程--线程空间操作(在线程函数中执行任务)--线程资源回收(回收栈区等资源)。1.线程创建使用pthread_create函数创建一个新线程函数原型如下int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);thread返回新创建的线程IDattr线程属性通常为NULLstart_routine线程执行的函数arg传递给线程函数的参数返回值成功0失败返回错误码2.获取线程IDpthread_t pthread_self(void);返回当前线程的ID。3.线程终止线程主动退出自杀void pthread_exit(void *retval);retval线程退出时返回的值 /需要返回出去的数据的指针。在外部指针指向的数据需要可以访问。取消其他线程他杀int pthread_cancel(pthread_t thread);终止线程ID为thread的线程。4.线程的回收手动回收方法int pthread_join(pthread_t thread, void **retval);功能说明该函数用于回收指定线程的资源具有阻塞等待特性。若目标线程未结束调用线程将阻塞直至目标线程终止。参数thread需回收的子线程IDpthread_t类型。retval用于接收子线程的退出状态或返回值需通过pthread_exit或返回指针传递。注意retval为二级指针void**需通过函数内部修改指针指向。禁止返回局部变量地址否则会导致野指针问题。返回值成功返回0失败返回-1并设置errno。关键点子线程可通过pthread_exit((void*)value)或return (void*)ptr传递返回值。重要原则默认情况下线程退出后栈区不会自动释放必须对非分离属性的线程调用pthread_join回收资源或设置其分离属性否则线程终止后其资源如8M栈内存不会释放造成资源泄露。示例代码#include pthread.h #include stdio.h #include stdlib.h #include string.h #include unistd.h void* th(void* arg) { // static char buf[]; char* buf malloc(100); strcpy(buf, 工作线程结束了\n);//将字符串拷贝到刚才分配的堆内存中 return buf; //子线程将堆内存的地址buf作为返回值返回 } int main(int argc, const char* argv[]) { pthread_t tid; //定义线程变量 pthread_create(tid, NULL, th, NULL); void* ret NULL; pthread_join(tid, ret);//主线程在这里阻塞等待直到子线程执行完毕 //ret: 将子线程的指针返回值(buf)通过指针的指针的方式存放到变量ret中 printf(ret:%s\n, (char*)ret); free(ret); return 0; }三、线程参数传递与返回值1.参数传递方式通过pthread_create的第四个参数arg传递数据。线程入口函数start_routine使用void* arg形参接收传递的数据。2.可传递的数据类型基本类型或字符串传递整型、字符、字符串常量或堆区字符串的地址。结构体在主线程中定义并填充结构体变量传递该变量的地址。在线程函数中将void* arg强制转换为对应的结构体指针以访问数据。3.线程返回值通过pthread_exit(void* retval)传递返回值。主线程使用pthread_join的第二个参数void** retval接收返回值的地址。返回值数据必须存储在有效内存区域堆内存或静态存储区避免使用线程栈上的局部变量。示例代码功能从终端读取用户输入的姓名、年龄、分数存入自定义结构体中同时预留了多线程的扩展逻辑#include pthread.h #include stdio.h #include stdlib.h #include string.h #include unistd.h /*功能从终端读取用户输入的姓名、年龄、分数存入自定义结构体中同时预留了多线程的扩展逻辑*/ typedef struct { char name[20]; int age; double score; } PER; //不懂的地方 void* th(void* arg) { PER* tmp (PER*)arg; //强制转换 //要传递的是 PER 结构体的地址所以需要把 void* 转换成 PER* printf(name:%s age:%d score:%.2f\n, tmp-name, tmp-age, tmp-score); } int main(int argc, char** argv) { pthread_t tid; PER per {0}; printf(pls input name:); fgets(per.name, sizeof(per.name), stdin); //关键处理fgets会把用户输入的换行符\n也读进来 // stdin标准输入终端表示从键盘读取输入 //这里把fgets读取的最后一个字符\n换成字符串结束符\0 per.name[strlen(per.name) - 1] \0; char tmp[10] {0}; printf(input age:); fgets(tmp, sizeof(tmp), stdin); // per.age atoi(tmp); // atoi把字符串转换成整型20 → 20 printf(input score:); fgets(tmp, sizeof(tmp), stdin); // per.score strtod(tmp, NULL); // strtod: 把字符串转换成double型98.5 → 98.5 return 0; }四、线程同步与互斥重要当多个线程共享资源临界资源时为防止数据竞争导致的不一致需要进行同步控制。1.互斥锁 (Mutex)概念提供对临界资源排他性访问的机制一次只允许一个线程进入临界区。编程步骤定义锁pthread_mutex_t mutex;初始化锁pthread_mutex_init(mutex, NULL);加锁pthread_mutex_lock(mutex);// 进入临界区前解锁pthread_mutex_unlock(mutex);// 离开临界区后销毁锁pthread_mutex_destroy(mutex);示例代码#include pthread.h #include stdio.h #include stdlib.h #include unistd.h int a 0; pthread_mutex_t mutex; void* th(void* arg) { int i 5000; while (i--) { pthread_mutex_lock(mutex); int tmp a; printf(a is %d\n, tmp 1); a tmp 1; //释放锁 pthread_mutex_unlock(mutex); } return NULL; } int main(int argc, char** argv) { pthread_t tid1, tid2; //互斥锁的初始化 pthread_mutex_init(mutex, NULL); //线程的创建 pthread_create(tid1, NULL, th, NULL); pthread_create(tid2, NULL, th, NULL); //线程阻塞等待 pthread_join(tid1, NULL); pthread_join(tid2, NULL); //互斥锁的释放 pthread_mutex_destroy(mutex); return 0; }互斥锁练习100张票在两个窗口卖#include pthread.h #include semaphore.h #include stdio.h #include stdlib.h #include string.h #include time.h #include unistd.h pthread_mutex_t mutex; int ticket 100; //总票数 void* th1(void* arg) { int win_id *(int*)arg; pthread_mutex_lock(mutex); //锁住窗口开始卖票 while (1) //持续售票 { if (ticket 0) { //开始卖,并打印卖票情况 ticket--; printf(窗口%d 卖出车票 %d\n, win_id, 100 - ticket); pthread_mutex_unlock(mutex); //卖完一张后解锁窗口 usleep(rand() % 50000); //办理卖票的业务 } else { pthread_mutex_unlock(mutex); // break; //票卖完退出循环 } } return NULL; } int main(int argc, char** argv) { srand(time(NULL)); //创建两个线程代表两个窗口 pthread_t tid1; pthread_t tid2; int win_id[2] {1, 2}; //窗口id pthread_mutex_init(mutex, NULL); pthread_create(tid1, NULL, th1, win_id); pthread_create(tid2, NULL, th1, win_id 1); //回收并销毁锁 pthread_join(tid1, NULL); pthread_join(tid2, NULL); pthread_mutex_destroy(mutex); return 0; }2.信号量 (Semaphore)概念不仅提供互斥还能控制线程执行的先后顺序。semaphore.h是POSIX信号量用于线程间同步。编程步骤1定义信号量sem_t sem;2初始化信号量sem_init(sem, 0, initial_value);//pshared参数为0表示线程间共享initial_value是信号量初值3P操作 (等待/申请资源)sem_wait(sem);// 信号量值减1如果为0则阻塞4V操作 (发信号/释放资源)sem_post(sem);// 信号量值加1唤醒等待线程5销毁信号量sem_destroy(sem);示例代码二值信号量常用于互斥初值为1。计数信号量信号量的第二种用法应用 互斥类的问题。 需要被保护的资源数不唯一。初值大于1用于控制对多个同类资源的访问。练习三个窗口有十个人来这里办业务#include pthread.h #include semaphore.h #include stdio.h #include stdlib.h #include time.h #include unistd.h sem_t sem_WIN; void* th(void* arg) { sem_wait(sem_WIN); printf(get win...\n); sleep(rand() % 5 1); // rand()%5是0到41是至少从一秒开始 printf(relese win...\n); sem_post(sem_WIN); return NULL; } int main(int argc, char** argv) { pthread_t tid[10] {0}; srand(time(NULL)); sem_init(sem_WIN, 0, 3); int i 0; for (i 0; i 10; i) { pthread_create(tid[i], NULL, th, NULL); } for (i 0; i 10; i) { pthread_join(tid[i], NULL); } sem_destroy(sem_WIN); return 0; }3.死锁定义多个线程因竞争资源而相互等待导致程序无法继续推进的现象。产生必要条件四个必须同时满足1) 互斥条件一个资源每次只能被一个线程使用。2) 请求与保持条件一个线程因请求资源而阻塞时对已获得的资源保持不放。3) 不剥夺条件线程已获得的资源在未使用完之前不能被强行剥夺。4) 循环等待条件若干线程之间形成一种头尾相接的循环等待资源关系。

更多文章